首页 Spring Security oAuth2应用
文章
取消

Spring Security oAuth2应用

数据表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
CREATE TABLE `clientdetails` (
  `appId` varchar(128) NOT NULL,
  `resourceIds` varchar(256) DEFAULT NULL,
  `appSecret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `grantTypes` varchar(256) DEFAULT NULL,
  `redirectUrl` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additionalInformation` varchar(4096) DEFAULT NULL,
  `autoApproveScopes` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`appId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_access_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  `authentication` blob,
  `refresh_token` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_approvals` (
  `userId` varchar(256) DEFAULT NULL,
  `clientId` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `status` varchar(10) DEFAULT NULL,
  `expiresAt` timestamp NULL DEFAULT NULL,
  `lastModifiedAt` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(128) NOT NULL,
  `resource_ids` varchar(256) DEFAULT NULL,
  `client_secret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `authorized_grant_types` varchar(256) DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` varchar(4096) DEFAULT NULL,
  `autoapprove` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_client_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_code` (
  `code` varchar(256) DEFAULT NULL,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_refresh_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

引入依赖

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.qjdmy</groupId>
        <artifactId>qjdmy-parent</artifactId>
        <version>1.0.0.RELEASE</version>
    </parent>

    <artifactId>qjdmy-oauth</artifactId>
    <version>1.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <url>http://www.qjdmy.com</url>
    <inceptionYear>2020-Now</inceptionYear>
    <description>认证与授权</description>

    <scm>
        <connection>scm:git:http://gitlab.tangyuewei.com/qjdmy/qjdmy-oauth.git</connection>
        <developerConnection>scm:git:http://gitlab.tangyuewei.com/qjdmy/qjdmy-oauth.git</developerConnection>
        <url>http://gitlab.tangyuewei.com/qjdmy/qjdmy-oauth</url>
        <tag>HEAD</tag>
    </scm>

    <developers>
        <developer>
            <id>tangyuewei</id>
            <name>Webster Tang</name>
            <email>[email protected]</email>
        </developer>
    </developers>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>


        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>


        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>qfdmy-repository-core</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>qfdmy-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <profiles>
        <profile>
            <id>microservice</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <configuration>
                            <mainClass>com.qjdmy.oauth.AuthApplication</mainClass>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
server:
  port: 9090

spring:
  application:
    name: qjdmy-oauth
  main:
    allow-bean-definition-overriding: true
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    jdbc-url: jdbc:mysql://mysql.tangyuewei.com:3306/qfdmy?serverTimezone=Asia/Shanghai&useLegacyDatetimeCode=false&nullNamePatternMatchesAll=true&zeroDateTimeBehavior=CONVERT_TO_NULL&tinyInt1isBit=false&autoReconnect=true&useSSL=false&pinGlobalTxToPhysicalConnection=true
    username: root
    password: 123456
    hikari:
      minimum-idle: 5
      idle-timeout: 600000
      maximum-pool-size: 10
      auto-commit: true
      pool-name: MyHikariCP
      max-lifetime: 1800000
      connection-timeout: 30000
      connection-test-query: SELECT 1

security:
  oauth2:
    client:
      client-id: dashboard
      client-secret: dashboard
      access-token-uri: http://localhost:${server.port}/oauth/token
      user-authorization-uri: http://localhost:${server.port}/oauth/authorize
    resource:
      token-info-uri: http://localhost:${server.port}/oauth/check_token
    authorization:
      check-token-access: http://localhost:${server.port}/oauth/check_token

Application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.qjdmy.oauth;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 认证与授权
 *
 * @author Webster
 * @since v1.0.0
 */
@SpringBootApplication(scanBasePackages = "com.qjdmy")
@MapperScan(basePackages = "com.qjdmy.repository.core.mapper")
public class AuthApplication {

    public static void main(String[] args) {
        SpringApplication.run(AuthApplication.class, args);
    }

}

自定义认证授权实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.qjdmy.oauth.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.qjdmy.repository.core.domain.CoreAdmin;
import com.qjdmy.repository.core.domain.CoreUser;
import com.qjdmy.repository.core.mapper.CoreAdminMapper;
import com.qjdmy.repository.core.mapper.CoreUserMapper;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * 自定义认证与授权
 * @author Webster
 * @since v1.0.0
 */
public class UserDetailsServiceImpl implements UserDetailsService {

    @Resource
    private CoreAdminMapper coreAdminMapper;

    @Resource
    private CoreUserMapper coreUserMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 管理后台
        LambdaQueryWrapper<CoreAdmin> adminWrapper = new LambdaQueryWrapper<>();
        adminWrapper.eq(CoreAdmin::getUsername, username);
        CoreAdmin coreAdmin = coreAdminMapper.selectOne(adminWrapper);
        if (null != coreAdmin) {
            // 授权,管理员权限为 ADMIN
            List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
            grantedAuthorities.add(new SimpleGrantedAuthority("ADMIN"));

            // 由框架完成认证工作
            return new User(coreAdmin.getUsername(), coreAdmin.getPassword(), grantedAuthorities);
        }

        // 门户网站
        LambdaQueryWrapper<CoreUser> userWrapper = new LambdaQueryWrapper<>();
        userWrapper.eq(CoreUser::getUsername, username);
        CoreUser coreUser = coreUserMapper.selectOne(userWrapper);
        if (null != coreUser) {
            List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
            grantedAuthorities.add(new SimpleGrantedAuthority("USERS"));
            return new User(coreUser.getUsername(), coreUser.getPassword(), grantedAuthorities);
        }

        return null;
    }
}

认证服务器配置

  • WebSecurityConfiguration.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.qjdmy.oauth.configuration;

import com.qjdmy.oauth.service.impl.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;

/**
 * 认证服务器配置
 *
 * @author Webster
 * @since v1.0.0
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

	@Bean
	@Override
	public UserDetailsService userDetailsServiceBean() {
		return new UserDetailsServiceImpl();
	}

	@Bean
	@Override
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(userDetailsServiceBean());
	}

	@Override
	public void configure(WebSecurity web) {
		// 忽略的访问路径
		web.ignoring()
				.antMatchers("/login/**")
				.antMatchers("/registry/user")
				.antMatchers("/logout/**");
	}

}
  • AuthorizationServerConfiguration.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package com.qjdmy.oauth.configuration;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * 认证服务器配置
 *
 * @author Webster
 * @since v1.0.0
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

	/**
	 * 注入用于支持 password 模式
	 */
	@Resource
	private AuthenticationManager authenticationManager;

	/**
	 * 默认的加密方式
	 * @return {@link BCryptPasswordEncoder}
	 */
	@Bean
	public BCryptPasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	/**
	 * Refresh Token 时需要自定义实现,否则抛异常 <br>
	 * Lazy 注解是为了防止循环注入(is there an unresolvable circular reference?)
	 */
	@Lazy
	@Resource(name = "userDetailsServiceBean")
	private UserDetailsService userDetailsService;

	@Bean
	@Primary
	@ConfigurationProperties(prefix = "spring.datasource")
	public DataSource dataSource() {
		// 配置数据源(注意,我使用的是 HikariCP 连接池),以上注解是指定数据源,否则会有冲突
		return DataSourceBuilder.create().build();
	}

	@Bean
	public TokenStore tokenStore() {
		// 基于 JDBC 实现,令牌保存到数据库
		return new JdbcTokenStore(dataSource());
	}

	@Bean
	public ClientDetailsService jdbcClientDetailsService() {
		// 基于 JDBC 实现,需要事先在数据库配置客户端信息
		return new JdbcClientDetailsService(dataSource());
	}

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		endpoints
				// 用于支持密码模式
				.authenticationManager(authenticationManager).tokenStore(tokenStore());

		// Refresh Token 时需要自定义实现,否则抛异常
		// Handling error: IllegalStateException, UserDetailsService is required.
		endpoints.userDetailsService(userDetailsService);
	}

	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
		security
				// 允许客户端访问 /oauth/check_token 检查 token
				.checkTokenAccess("isAuthenticated()").allowFormAuthenticationForClients();
	}

	/**
	 * 配置客户端
	 * @param clients {@link ClientDetailsServiceConfigurer}
	 * @throws Exception 全局异常
	 */
	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		// 客户端配置
		clients.withClientDetails(jdbcClientDetailsService());
	}

}

实现登录功能

登录业务接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.qjdmy.oauth.service;

import java.util.Map;

/**
 * 登录
 * @author Webster
 * @since v1.0.0
 */
public interface ILoginService {
    /**
     * 登录成功后仅返回 Token
     * @param username {@code String} 账号
     * @param password {@code String} 密码
     * @return {@code Map<String, String>} key: token
     */
    Map<String, String> getToken(String username, String password);

    /**
     * 刷新 Token
     * @param accessToken {@code String} 使用旧 Token 换新 Token
     * @return {@code Map<String, String>} 新 Token,key: token
     */
    Map<String, String> refresh(String accessToken);
}

登录业务实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package com.qjdmy.oauth.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.qjdmy.commons.exceptions.BusinessException;
import com.qjdmy.commons.response.ResponseCode;
import com.qjdmy.oauth.service.ILoginService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

/**
 * 登录
 * @author Webster
 * @since v1.0.0
 */
@Service
public class LoginServiceImpl implements ILoginService {
    /**
     * TODO 用于临时存放所有 Refresh Token,实际情况应该放在 Redis 中
     */
    private static Map<String, String> refreshTokenMaps = new HashMap<>();

    @Value("${security.oauth2.client.access-token-uri}")
    private String accessTokenUri;

    @Value("${security.oauth2.client.client-id}")
    private String clientId;

    @Value("${security.oauth2.client.client-secret}")
    private String clientSecret;

    @Resource
    private BCryptPasswordEncoder passwordEncoder;

    @Resource(name = "userDetailsServiceBean")
    private UserDetailsService userDetailsService;

    @Override
    public Map<String, String> getToken(String username, String password) {
        Map<String, String> result = new HashMap<>();

        // 验证密码是否正确
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        if (userDetails == null || !passwordEncoder.matches(password, userDetails.getPassword())) {
            throw new BusinessException(ResponseCode.USER_LOGIN_ERROR);
        }

        // 通过 HTTP 客户端请求登录接口
        Map<String, Object> authParam = getAuthParam();
        authParam.put("username", username);
        authParam.put("password", password);
        authParam.put("grant_type", "password");

        // 获取 access_token
        String strJson = HttpUtil.post(accessTokenUri, authParam);
        JSONObject jsonObject = JSONUtil.parseObj(strJson);
        String token = String.valueOf(jsonObject.get("access_token"));
        String refresh = String.valueOf(jsonObject.get("refresh_token"));
        if (StrUtil.isNotBlank(token) && StrUtil.isNotBlank(refresh)) {
            // 将 refresh_token 保存在服务端
            refreshTokenMaps.put(token, refresh);

            // 将 access_token 返回给客户端
            result.put("token", token);
            return result;
        }

        return null;
    }

    @Override
    public Map<String, String> refresh(String accessToken) {
        Map<String, String> result = new HashMap<>();

        // Access Token 不存在直接返回 null
        String refreshToken = refreshTokenMaps.get(accessToken);
        if (StrUtil.isBlank(refreshToken)) {
            throw new BusinessException(ResponseCode.USER_NOT_LOGGED_IN);
        }

        // 通过 HTTP 客户端请求登录接口
        Map<String, Object> authParam = getAuthParam();
        authParam.put("grant_type", "refresh_token");
        authParam.put("refresh_token", refreshToken);

        // 获取 access_token
        String strJson = HttpUtil.post(accessTokenUri, authParam);
        JSONObject jsonObject = JSONUtil.parseObj(strJson);
        String token = String.valueOf(jsonObject.get("access_token"));
        String refresh = String.valueOf(jsonObject.get("refresh_token"));
        if (StrUtil.isNotBlank(token) && StrUtil.isNotBlank(refresh)) {
            // 删除旧 Token
            refreshTokenMaps.remove(accessToken);

            // 将 refresh_token 保存在服务端
            refreshTokenMaps.put(token, refresh);

            // 将 access_token 返回给客户端
            result.put("token", token);
            return result;
        }

        return null;
    }

    // 私有方法 ------------------------------------------- Begin

    private Map<String, Object> getAuthParam() {
        Map<String, Object> param = new HashMap<>();
        param.put("client_id", clientId);
        param.put("client_secret", clientSecret);
        return param;
    }
}

封装工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.qjdmy.commons.web;

import cn.hutool.core.util.StrUtil;
import com.qjdmy.commons.exceptions.BusinessException;
import com.qjdmy.commons.response.ResponseCode;

/**
 * 请求头处理
 * @author Webster
 * @since v1.0.0
 */
public class Header {

    private static final String AUTHORIZATION_BEARER_TOKEN = "Basic ";

    /**
     * 获取 Token
     * @param header {@code String} request.getHeader("Authorization")
     * @return {@code String} token
     */
    public static String getAuthorization(String header) {
        if (StrUtil.isBlank(header) || header.startsWith(AUTHORIZATION_BEARER_TOKEN)) {
            throw new BusinessException(ResponseCode.USER_NOT_LOGGED_IN);
        }
        return header.substring(AUTHORIZATION_BEARER_TOKEN.length() + 1);
    }
}

封装请求参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.qjdmy.oauth.controller.param;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * 登录参数
 * @author Webster
 * @since v1.0.0
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class LoginParam implements Serializable {

    private static final long serialVersionUID = 6227804428105653962L;

    /**
     * 账号
     */
    private String username;

    /**
     * 密码
     */
    private String password;

}

请求处理代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.qjdmy.oauth.controller;

import com.qfdmy.commons.response.ResponseResult;
import com.qfdmy.commons.web.Header;
import com.qfdmy.oauth.controller.param.LoginParam;
import com.qfdmy.oauth.service.ILoginService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * 登录
 * @author Webster
 * @since v1.0.0
 */
@RestController
@RequestMapping(value = "login")
public class LoginController {

    @Resource
    private HttpServletRequest request;

    @Resource
    private ILoginService loginService;

    /**
     * 管理员登录
     * @param loginParam {@code JSON} {@link LoginParam}
     * @return {@link ResponseResult}
     */
    @PostMapping("admin")
    public ResponseResult admin(@RequestBody LoginParam loginParam) {
        return ResponseResult.success(loginService.getToken(loginParam.getUsername(), loginParam.getPassword()));
    }

    /**
     * 用户登录,登录只是拿 Token
     * @param loginParam {@code JSON} {@link LoginParam}
     * @return {@link ResponseResult}
     */
    @PostMapping("user")
    public ResponseResult users(@RequestBody LoginParam loginParam) {
        return ResponseResult.success(loginService.getToken(loginParam.getUsername(), loginParam.getPassword()));
    }

    /**
     * 刷新令牌
     * @return {@link ResponseResult}
     */
    @PostMapping("refresh")
    public ResponseResult refresh() {
        String token = Header.getAuthorization(request.getHeader("Authorization"));
        return ResponseResult.success(loginService.refresh(token));
    }
}

实现注销功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.qjdmy.oauth.controller;

import com.qjdmy.commons.response.ResponseCode;
import com.qjdmy.commons.response.ResponseResult;
import com.qjdmy.commons.web.Header;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * 注销
 *
 * @author Webster
 * @since v1.0.0
 */
@RestController
@RequestMapping(value = "logout")
public class LogoutController {
    @Resource
    public TokenStore tokenStore;

    @Resource
    public HttpServletRequest request;

    /**
     * 注销管理员
     *
     * @return {@link ResponseResult}
     */
    @PostMapping("admin")
    public ResponseResult admin() {
        return logout();
    }

    /**
     * 注销用户
     *
     * @return {@link ResponseResult}
     */
    @PostMapping("user")
    public ResponseResult users() {
        return logout();
    }

    // 私有方法 ------------------------------------------- Begin

    private ResponseResult logout() {
        String token = Header.getAuthorization(request.getHeader("Authorization"));

        // 删除 token 以注销
        OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token);
        if (null != oAuth2AccessToken) {
            tokenStore.removeAccessToken(oAuth2AccessToken);
            return ResponseResult.success();
        }

        return ResponseResult.failure(ResponseCode.INTERFACE_ADDRESS_INVALID);
    }
}

资源服务器配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.qjdmy.all.configuration;

import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

/**
 * 资源服务器配置
 *
 * @author Webster
 * @since v1.0.0
 */
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    /**
     * 管理员角色
     */
    private static final String ADMIN = "ADMIN";

    /**
     * 用户角色
     */
    private static final String USERS = "USERS";

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // 允许访问全部资源
//		http.exceptionHandling().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//				.and().authorizeRequests().antMatchers("/**").permitAll();

        // 管理员授权请求路径
        String[] adminPaths = new String[]{
                "/core/**", "/qiniu/**"
        };

        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry
                expressionInterceptUrlRegistry = http.exceptionHandling()
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().authorizeRequests();
        for (String adminPath : adminPaths) {
            expressionInterceptUrlRegistry.antMatchers(adminPath).hasAnyAuthority(ADMIN);
        }
    }

}
本文由作者按照 CC BY 4.0 进行授权
载入天数...载入时分秒...