Spring Security OAuth2 JWT 认证服务器配置

四种授权模式

  • 授权码模式
  • 密码模式
  • 客户端模式
  • 简化模式

密码模式

  • grant_type:授权类型,必选,此处固定值password
  • username:表示用户名,必选
  • password:表示用户密码,必选
  • scope:权限范围,可选

(1)获取access_token

1
http://localhost:9001/oauth/token?username=user&password=secret&grant_type=password&client_id=client_password&client_secret=secret
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOlt7ImF1dGhvcml0eSI6IlJPTEUifV0sImF1ZCI6WyJyZXNvdXJjZV9wYXNzd29yZF9pZCJdLCJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsidGVzdCIsImNlc2hpIl0sImV4cCI6MTU2NTM0NzQ3MiwiYXV0aG9yaXRpZXMiOlsiUk9MRSJdLCJqdGkiOiJlNTc5ZjVlYy1kNjQ1LTQ1OWYtOWM1Mi05NjllYjI5YzFjMjUiLCJjbGllbnRfaWQiOiJjbGllbnRfcGFzc3dvcmQiLCJ1c2VybmFtZSI6InVzZXIifQ.igWUEbbR-wNvDCW66OPJ4xFfIjVc8UFA28NnJXLr4KA",
"token_type":"bearer",
"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOlt7ImF1dGhvcml0eSI6IlJPTEUifV0sImF1ZCI6WyJyZXNvdXJjZV9wYXNzd29yZF9pZCJdLCJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsidGVzdCIsImNlc2hpIl0sImF0aSI6ImU1NzlmNWVjLWQ2NDUtNDU5Zi05YzUyLTk2OWViMjljMWMyNSIsImV4cCI6MTU2NTM0Nzc3MiwiYXV0aG9yaXRpZXMiOlsiUk9MRSJdLCJqdGkiOiI0NjUzNDVkNy00ZTIxLTQxMzQtYTNlNC0xNjY4Yzk5NWJkYjMiLCJjbGllbnRfaWQiOiJjbGllbnRfcGFzc3dvcmQiLCJ1c2VybmFtZSI6InVzZXIifQ.oxLvqsVgD2IfpcxW52K7eJjqU4JJ4eadrHvJtzMo85A",
"expires_in":899,
"scope":"test ceshi",
"principal":[
{
"authority":"ROLE"
}
],
"username":"user",
"jti":"e579f5ec-d645-459f-9c52-969eb29c1c25"
}

(2)通过refresh_token获取access_token

1
http://localhost:9001/oauth/token?grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOlt7ImF1dGhvcml0eSI6IlJPTEUifV0sImF1ZCI6WyJyZXNvdXJjZV9wYXNzd29yZF9pZCJdLCJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsidGVzdCIsImNlc2hpIl0sImF0aSI6ImU1NzlmNWVjLWQ2NDUtNDU5Zi05YzUyLTk2OWViMjljMWMyNSIsImV4cCI6MTU2NTM0Nzc3MiwiYXV0aG9yaXRpZXMiOlsiUk9MRSJdLCJqdGkiOiI0NjUzNDVkNy00ZTIxLTQxMzQtYTNlNC0xNjY4Yzk5NWJkYjMiLCJjbGllbnRfaWQiOiJjbGllbnRfcGFzc3dvcmQiLCJ1c2VybmFtZSI6InVzZXIifQ.oxLvqsVgD2IfpcxW52K7eJjqU4JJ4eadrHvJtzMo85A&client_id=client_password&client_secret=secret
1
2
3
4
5
6
7
8
9
10
{
"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOiJ1c2VyIiwiYXVkIjpbInJlc291cmNlX3Bhc3N3b3JkX2lkIl0sInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyJ0ZXN0IiwiY2VzaGkiXSwiZXhwIjoxNTY1MzQ3NzM3LCJhdXRob3JpdGllcyI6WyJST0xFIl0sImp0aSI6IjJjZGY4YTgyLTE1NzEtNDI0YS05MjFkLTU2MDQzZTkxN2EwNyIsImNsaWVudF9pZCI6ImNsaWVudF9wYXNzd29yZCIsInVzZXJuYW1lIjoidXNlciJ9.lHVuK9L1ViegxnLmd0RijgJjfKliOCoCVa7taN35S8k",
"token_type":"bearer",
"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOiJ1c2VyIiwiYXVkIjpbInJlc291cmNlX3Bhc3N3b3JkX2lkIl0sInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyJ0ZXN0IiwiY2VzaGkiXSwiYXRpIjoiMmNkZjhhODItMTU3MS00MjRhLTkyMWQtNTYwNDNlOTE3YTA3IiwiZXhwIjoxNTY1MzQ3NzcyLCJhdXRob3JpdGllcyI6WyJST0xFIl0sImp0aSI6IjQ2NTM0NWQ3LTRlMjEtNDEzNC1hM2U0LTE2NjhjOTk1YmRiMyIsImNsaWVudF9pZCI6ImNsaWVudF9wYXNzd29yZCIsInVzZXJuYW1lIjoidXNlciJ9.wziLAaYVxGswJNS_Wal_SE6lNsDd14f4a7UrTFoY2rM",
"expires_in":899,
"scope":"test ceshi",
"principal":"user",
"username":"user",
"jti":"2cdf8a82-1571-424a-921d-56043e917a07"
}

授权码模式

  • client_id:客户端ID,必选
  • response_type:必须为code,必选
  • redirect_uri:回掉url,必选

(1)获取授权码

1
http://localhost:9001/oauth/authorize?client_id=auth_code&response_type=code&redirect_uri=http://localhost:9001/auth_user/get_auth_code

(2)获取access_token

1
http://localhost:9001/oauth/token?grant_type=authorization_code&code=QjDs33&client_id=auth_code&client_secret=secret&redirect_uri=http://localhost:9001/auth_user/get_auth_code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOlt7ImF1dGhvcml0eSI6IlJPTEUifV0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2VsZWN0IiwicmVwbGFjZSIsImluc2VydCIsInVwZGF0ZSIsImRlbCJdLCJleHAiOjE1NjUzNDI3NDYsImF1dGhvcml0aWVzIjpbIlJPTEUiXSwianRpIjoiNDUzOTY1ZWYtNDdiOC00MTc4LWFmYWUtNzlmMzljODhjZWEzIiwiY2xpZW50X2lkIjoiYXV0aF9jb2RlIiwidXNlcm5hbWUiOiJhZG1pbiJ9.qvjdiaTXjQVYikgUnMGvssZKwILiuzi9mAS65R1lzfY",
"token_type":"bearer",
"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOlt7ImF1dGhvcml0eSI6IlJPTEUifV0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2VsZWN0IiwicmVwbGFjZSIsImluc2VydCIsInVwZGF0ZSIsImRlbCJdLCJhdGkiOiI0NTM5NjVlZi00N2I4LTQxNzgtYWZhZS03OWYzOWM4OGNlYTMiLCJleHAiOjE1NjUzNDMwNDYsImF1dGhvcml0aWVzIjpbIlJPTEUiXSwianRpIjoiYjk3OWYwNmUtMDgyNy00MjhlLTkxNmUtZDdkNTljOThlNzJlIiwiY2xpZW50X2lkIjoiYXV0aF9jb2RlIiwidXNlcm5hbWUiOiJhZG1pbiJ9.HYhycwL9lT56yMuJu5y0hz7BfPhd1Gw3eweQWsWodQA",
"expires_in":899,
"scope":"select replace insert update del",
"principal":[
{
"authority":"ROLE"
}
],
"username":"admin",
"jti":"453965ef-47b8-4178-afae-79f39c88cea3"
}

(3)通过refresh_token获取access_token

1
http://localhost:9001/oauth/token?grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOlt7ImF1dGhvcml0eSI6IlJPTEUifV0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2VsZWN0IiwicmVwbGFjZSIsImluc2VydCIsInVwZGF0ZSIsImRlbCJdLCJhdGkiOiI0NTM5NjVlZi00N2I4LTQxNzgtYWZhZS03OWYzOWM4OGNlYTMiLCJleHAiOjE1NjUzNDMwNDYsImF1dGhvcml0aWVzIjpbIlJPTEUiXSwianRpIjoiYjk3OWYwNmUtMDgyNy00MjhlLTkxNmUtZDdkNTljOThlNzJlIiwiY2xpZW50X2lkIjoiYXV0aF9jb2RlIiwidXNlcm5hbWUiOiJhZG1pbiJ9.HYhycwL9lT56yMuJu5y0hz7BfPhd1Gw3eweQWsWodQA&client_id=auth_code&client_secret=secret
1
2
3
4
5
6
7
8
9
10
{
"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOiJhZG1pbiIsInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2VsZWN0IiwicmVwbGFjZSIsImluc2VydCIsInVwZGF0ZSIsImRlbCJdLCJleHAiOjE1NjUzNDI4MjMsImF1dGhvcml0aWVzIjpbIlJPTEUiXSwianRpIjoiNTc1MDIyM2YtZGVkYi00OTQyLWE3OWYtNGM0NzkwNjRiZDlhIiwiY2xpZW50X2lkIjoiYXV0aF9jb2RlIiwidXNlcm5hbWUiOiJhZG1pbiJ9.uUEnoekC-ZESh1ongSsbwOx0ZtsVxV4L2WWnNb42NXQ",
"token_type":"bearer",
"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcmluY2lwYWwiOiJhZG1pbiIsInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2VsZWN0IiwicmVwbGFjZSIsImluc2VydCIsInVwZGF0ZSIsImRlbCJdLCJhdGkiOiI1NzUwMjIzZi1kZWRiLTQ5NDItYTc5Zi00YzQ3OTA2NGJkOWEiLCJleHAiOjE1NjUzNDMwNDYsImF1dGhvcml0aWVzIjpbIlJPTEUiXSwianRpIjoiYjk3OWYwNmUtMDgyNy00MjhlLTkxNmUtZDdkNTljOThlNzJlIiwiY2xpZW50X2lkIjoiYXV0aF9jb2RlIiwidXNlcm5hbWUiOiJhZG1pbiJ9.wefqVCYmbQ7hgTRHFAYCxtElvB_iuE69sOSXbxiMbH8",
"expires_in":899,
"scope":"select replace insert update del",
"principal":"admin",
"username":"admin",
"jti":"5750223f-dedb-4942-a79f-4c479064bd9a"
}

Client模式

  • client_id: 客户端ID,必选
  • client_secret: 客户端密码,必选
  • grant_type: 必须为password,必选
  • scope: 授权范围,必选
1
http://localhost:9001/oauth/token?grant_type=client_credentials&scope=insert&client_id=client&client_secret=secret
1
2
3
4
5
6
7
{
"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJpbnNlcnQiXSwiZXhwIjoxNTY1MzQ4MTEwLCJqdGkiOiIzOGI3ODMzYS1kMGIxLTQzMjYtODIxYy00YTc2NmM4ZGVhZGEiLCJjbGllbnRfaWQiOiJjbGllbnQifQ.W0ANHlumi1nYHp7wesg9_oU6tYCMDXDE68hlM2jT8ng",
"token_type":"bearer",
"expires_in":899,
"scope":"insert",
"jti":"38b7833a-d0b1-4326-821c-4a766c8deada"
}

极简模式

1
http://localhost:9001/oauth/authorize?client_id=client_implicit&response_type=token&redirect_uri=http://localhost:9001/auth_user/get_token_info
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
{
"authorities":[
{
"authority":"ROLE"
}
],
"details":{
"remoteAddress":"0:0:0:0:0:0:0:1",
"sessionId":"15672F0BFB67D987A68E18C4219FFA1F"
},
"authenticated":true,
"principal":{
"password":null,
"username":"admin",
"authorities":[
{
"authority":"ROLE"
}
],
"accountNonExpired":true,
"accountNonLocked":true,
"credentialsNonExpired":true,
"enabled":true
},
"credentials":null,
"name":"admin"
}

依赖pom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

认证服务器配置

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
/**
* @Description: @EnableAuthorizationServer注解开启OAuth2授权服务机制
* @Package: AuthorizationServerConfig
* @Version: 1.0
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private OAuth2ClientDetailsService oAuth2ClientDetailsService;

@Autowired
private AuthUserDetailsService authUserDetailsService;

/**
* 用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里初始化,
* 可以把客户端详情信息写死也可以写入内存或者数据库中
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//使用自定义ClientDetailsService初始化配置
clients.withClientDetails(oAuth2ClientDetailsService);
}

/**
* 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
DefaultTokenServices tokenServices = new DefaultTokenServices();
//token持久化容器
tokenServices.setTokenStore(tokenStore());
//客户端信息
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
//自定义token生成
tokenServices.setTokenEnhancer(accessTokenConverter());
//access_token 的有效时长 (秒), 默认 12 小时
tokenServices.setAccessTokenValiditySeconds(60 * 15);
//refresh_token 的有效时长 (秒), 默认 30 天
tokenServices.setRefreshTokenValiditySeconds(60 * 20);
//是否支持refresh_token, 默认false
tokenServices.setSupportRefreshToken(true);
//是否复用refresh_token, 默认为true(如果为false, 则每次请求刷新都会删除旧的refresh_token, 创建新的refresh_token)
tokenServices.setReuseRefreshToken(true);

endpoints
//通过authenticationManager开启密码授权
.authenticationManager(authenticationManager)
//自定义refresh_token刷新令牌对用户信息的检查,以确保用户信息仍然有效
.userDetailsService(authUserDetailsService)
//token相关服务
.tokenServices(tokenServices)
//控制TokenEndpoint端点请求访问的类型,默认HttpMethod.POST
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
/**
pathMapping用来配置端点URL链接,第一个参数是端点URL默认地址,第二个参数是你要替换的URL地址
上面的参数都是以“/”开头,框架的URL链接如下:
/oauth/authorize: 授权端点, 对应的类: AuthorizationEndpoint.java
/oauth/token: 令牌端点, 对应的类: TokenEndpoint.java
/oauth/confirm_access: 用户确认授权提交端点. 对应的类: WhitelabelApprovalEndpoint.java
/oauth/error: 授权服务错误信息端点
/oauth/check_token: 用于资源服务访问的令牌解析端点
/oauth/token_key: 提供公有密匙的端点, 如果你使用JWT令牌的话
*/
.pathMapping("/oauth/confirm_access", "/custom/confirm_access");
}

/**
* 用来配置令牌端点(Token Endpoint)的安全约束
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.realm("OAuth2-Sample")
.allowFormAuthenticationForClients()
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()");
}

/**
* OAuth2 token持久化接口
*/
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}

/**
* 自定义token令牌增强器
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new UserTokenEnhancer();
accessTokenConverter.setSigningKey("123456");
return accessTokenConverter;
}
}

自定义ClientDetailsService实现类

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
/**
* @Description: 用户认证
* @Package: cn.appblog.security.oauth2.po.AuthUserDetailsService
* @Version: 1.0
*/
@Component
public class AuthUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;

/**
* 根据用户名查询用户角色、权限等信息
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("用户信息验证: " + username);
/**
isEnabled 账户是否启用
isAccountNonExpired 账户没有过期
isCredentialsNonExpired 身份认证是否是有效的
isAccountNonLocked 账户没有被锁定
*/
return new User(username, passwordEncoder.encode("secret"),
true,
true,
true,
true,
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE"));
}
}

自定义WebSecurityConfigurerAdapter实现类

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
/**
* @Description: 启动基于Spring Security的安全认证
* @Package: com.yaomy.security.oauth2.config.WebSecurityConfigurer
* @Version: 1.0
*/
@Configuration
@EnableWebSecurity
public class BaseSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthUserDetailsService authUserDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserAccessDeniedHandler accessDeniedHandler;
@Autowired
private UserAuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private UserAuthenticationFailureHandler authenticationFailureHandler;
@Autowired
private UserAuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private UserLogoutSuccessHandler logoutSuccessHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 指定支持基于表单的身份验证, 如果未指定FormLoginConfigurer#loginPage(String), 则将生成默认登录页面
.formLogin()
// 自定义登录页url, 默认为/login
.loginPage("/test/login")
// 登录请求拦截的url, 也就是form表单提交时指定的action
.loginProcessingUrl("/user/login")
// 用户名的请求字段 username
.usernameParameter("username")
// 密码的请求字段 默认为password
.passwordParameter("password")
// 登录成功
// .successHandler(authenticationSuccessHandler)
// 登录失败
.failureHandler(authenticationFailureHandler)
// 无条件允许访问
.permitAll()
.and()
.requestMatchers()
.anyRequest()
.and()
.authorizeRequests()
.antMatchers("/oauth/**")
.permitAll()
/* .and()
// 其它的请求要求必须有身份认证
.authorizeRequests()
.anyRequest()
.authenticated()
*/
.and()
.logout()
.logoutSuccessHandler(logoutSuccessHandler)
.permitAll()
.and()
// 认证过的用户访问无权限资源时的处理
.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
http.csrf().disable();
}

/**
* Spring Security认证服务中的相关实现重新定义
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 加入自定义的安全认证
auth.userDetailsService(this.authUserDetailsService).passwordEncoder(this.passwordEncoder());
auth.authenticationProvider(this.authenticationProvider());
}

/**
* 需要主动暴漏AuthenticationManager,否则会报异常
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

/**
* Spring security认证Bean
*/
@Bean
public AuthenticationProvider authenticationProvider() {
AuthenticationProvider authenticationProvider = new UserAuthenticationProvider();
return authenticationProvider;
}

/**
* 自定义加密器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

自定义JWT 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
/**
* @Description: 用户自定义token令牌,包括access_token和refresh_token
* @Package: cn.appblog.security.oauth2.enhancer.UserTokenEnhancer
* @Version: 1.0
*/
public class UserTokenEnhancer extends JwtAccessTokenConverter {
/**
* 重新定义令牌token
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
//授权类型 authorization_code、password、client_credentials、refresh_token、implicit
String grantType = authentication.getOAuth2Request().getGrantType();
if (!StringUtils.equals(grantType, "client_credentials")) {
String userName = authentication.getUserAuthentication().getName();
// 与登录时候放进去的UserDetail实现类一直查看link{SecurityConfiguration}
Object principal = authentication.getUserAuthentication().getPrincipal();
/**
自定义一些token属性
**/
Map<String, Object> additionalInformation = Maps.newHashMap();
additionalInformation.put("username", userName);
if (principal instanceof User) {
additionalInformation.put("principal", ((User) principal).getAuthorities());
} else {
additionalInformation.put("principal", principal);
}
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);

}
OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication);
return enhancedToken;
}
}

用户自定义身份认证AuthenticationProvider

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
/**
* @Description: 用户自定义身份认证
* @Package: cn.appblog.security.oauth2.provider.MyAuthenticationProvider
* @Version: 1.0
*/
@Slf4j
@Component
public class UserAuthenticationProvider implements AuthenticationProvider {
@Autowired
private AuthUserDetailsService authUserDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;

/**
* 认证处理,返回一个Authentication的实现类则代表认证成功,返回null则代表认证失败
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
//获取用户信息
UserDetails user = authUserDetailsService.loadUserByUsername(username);
log.info("password: {}, user.getPassword(): {}", password, user.getPassword());
log.info("passwordEncoder.matches: {}", passwordEncoder.matches(password, user.getPassword()));
//比较前端传入的密码明文和数据库中加密的密码是否相等
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new DisabledException("用户密码不正确");
}
//获取用户权限信息
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(user, password, authorities);
}

/**
* 如果该AuthenticationProvider支持传入的Authentication对象,则返回true
*/
@Override
public boolean supports(Class<?> aClass) {
return aClass.equals(UsernamePasswordAuthenticationToken.class);
}
}

自定义登陆页面

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
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>

<style>
.login-container {
margin: 50px;
width: 100%;
}
.form-container {
margin: 0px auto;
width: 50%;
text-align: center;
box-shadow: 1px 1px 10px #888888;
height: 300px;
padding: 5px;
}
input {
margin-top: 10px;
width: 350px;
height: 30px;
border-radius: 3px;
border: 1px #E9686B solid;
padding-left: 2px;
}
.btn {
width: 350px;
height: 35px;
line-height: 35px;
cursor: pointer;
margin-top: 20px;
border-radius: 3px;
background-color: #E9686B;
color: white;
border: none;
font-size: 15px;
}
.title{
margin-top: 5px;
font-size: 18px;
color: #E9686B;
}
</style>
<body>
<div class="login-container">
<div class="form-container">
<p class="title">用户登录</p>
<form name="loginForm" method="post" th:action="${loginProcessUrl}">
<input type="text" name="username" placeholder="用户名"/>
<br>
<input type="password" name="password" placeholder="密码"/>
<br>
<button type="submit" class="btn">登 &nbsp;&nbsp; 录</button>
</form>
</div>
</div>
</body>
</html>

自定义授权页面

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
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>授权</title>
</head>
<style>
html{
padding: 0px;
margin: 0px;
}
.title {
background-color: #E9686B;
height: 50px;
padding-left: 20%;
padding-right: 20%;
color: white;
line-height: 50px;
font-size: 18px;
}
.title-left{
float: right;
}
.title-right{
float: left;
}
.title-left a{
color: white;
}
.container{
clear: both;
text-align: center;
}
.btn {
width: 350px;
height: 35px;
line-height: 35px;
cursor: pointer;
margin-top: 20px;
border-radius: 3px;
background-color: #E9686B;
color: white;
border: none;
font-size: 15px;
}
</style>
<body style="margin: 0px">
<div class="title">
<div class="title-right">Spring Security 授权</div>
<div class="title-left">
<a href="#help">帮助</a>
</div>
</div>
<div class="container">
<h3 th:text="${clientId}+' 请求授权,该应用将获取你的以下信息'"></h3>
<form method="post" action="/oauth/authorize">
<input type="hidden" name="user_oauth_approval" value="true">
<!--<input type="hidden" name="_csrf" th:value="${_csrf.getToken()}"/>-->
<ul style="list-style-type: none">
<li th:each="s:${scope}">
<div class="form-group"><a th:text="${s}"></a>: <input type="radio" th:name="'scope.'+${s}" value="true">Approve(授权) <input type="radio" th:name="'scope.'+${s}" value="false" checked="">Deny(拒绝)</div>
</li>
</ul>
授权后表明你已同意 <a href="#boot" style="color: #E9686B">服务协议</a><br>
<button class="btn" type="submit"> 同意/授权</button>
</form>
</div>
</body>
</html>

登录页及授权页渲染

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
/**
* @Description: 自定义登陆页面
* @Package: GrantController
* @Version: 1.0
*/
@Controller
@SessionAttributes("authorizationRequest")
public class GrantController {
@GetMapping("/test/login")
public String index(Model model) {
model.addAttribute("loginProcessUrl", "/user/login");
return "login";
}

@RequestMapping("/custom/confirm_access")
public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) throws Exception {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest");
ModelAndView view = new ModelAndView();
view.setViewName("grant");
view.addObject("clientId", authorizationRequest.getClientId());
view.addObject("scope", authorizationRequest.getScope());
System.out.println(authorizationRequest.getScope());
System.out.println(authorizationRequest.getClientId());
return view;
}
}

自定义授权重定向相关接口

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
/**
* @Description: 登录测试接口
* @Package: LoginController
* @Version: 1.0
*/
@RestController
@RequestMapping("auth_user")
public class LoginController {
/**
* 登录测试接口
*/
@RequestMapping(value = "get_token_info", method = RequestMethod.GET)
public Object login(HttpServletRequest request) {
SecurityContext ctx = SecurityContextHolder.getContext();
Authentication auth = ctx.getAuthentication();
System.out.println(auth.getAuthorities() + " -- " + auth.getCredentials() + " -- " + auth.getDetails() + " -- " + auth.getPrincipal() + " -- " + auth.getName());
return auth;
}

/**
* 获取授权码
*/
@RequestMapping("/get_auth_code")
public String home(HttpServletRequest request) {
String code = request.getParameter("code");
System.out.println("code: " + code);
if (StringUtils.isBlank(code)) {
code = "denyAll";
}
return code;
}
}

本文转载参考 原文 并加以调试

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2020 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :