说明
spring-security-oauth
这个项目不赞成使用了。oauth2已经由Spring Security提供服务。Spring Security没有提供对认证服务器的支持,需要 spring-authorization-server
去支持。
https://spring.io/blog/2020/04/15/announcing-the-spring-authorization-server
The Spring Security OAuth project is deprecated. The latest OAuth 2.0 support is provided by Spring Security. See the OAuth 2.0 Migration Guide for further details.
Since Spring Security doesn’t provide Authorization Server support, migrating a Spring Security OAuth Authorization Server see https://spring.io/blog/2020/04/15/announcing-the-spring-authorization-server
- https://github.com/spring-projects/spring-security-oauth
- https://github.com/spring-projects/spring-security-oauth2-boot
- https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide
虽然已经不推荐使用,但是很多的老项目还是使用的Spring Security Oauth,所以还是很有必要学习一下的。
环境搭建
- 添加依赖
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.5.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 授权服务
AuthorizationServer.java
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("tokenServices")
private AuthorizationServerTokenServices authorizationServerTokenServices;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
.tokenKeyAccess("permitAll()") // oauth/token_key是公开
.checkTokenAccess("permitAll()") // oauth/check_token公开
.allowFormAuthenticationForClients() //密码模式:表单认证(申请令牌)
;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()// 使用in-memory存储
.withClient("c")// client_id
.secret("123456")//客户端密钥
.resourceIds("res")//资源列表
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")// 该client允许的授权类型authorization_code,password,refresh_token,implicit,client_credentials
.scopes("all")// 允许的授权范围
.autoApprove(false)//false跳转到授权页面
//加上验证回调地址
.redirectUris("http://www.baidu.com");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.userDetailsService(userDetailsService)
.authenticationManager(authenticationManager)// 认证管理器 => 密码模式需要在认证服务器中设置 中配置AuthenticationManager
.tokenServices(authorizationServerTokenServices)
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
}
endpoints中添加 tokenService
- 认证服务安全配置
SecurityConfig.java
@Configuration
public class SecurityConfig {
private String SIGNING_KEY = "uaa123";
@Bean
public TokenStore tokenStore() {
//使用内存存储令牌(普通令牌)
// return new InMemoryTokenStore();
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY); //对称秘钥,资源服务器使用该秘钥来验证
return converter;
}
// 令牌管理服务
@Bean("tokenServices")
public AuthorizationServerTokenServices tokenService(ClientDetailsService clientDetailsService, TokenStore tokenStore) {
DefaultTokenServices service=new DefaultTokenServices();
service.setClientDetailsService(clientDetailsService);//客户端详情服务
service.setSupportRefreshToken(true);//支持刷新令牌
service.setTokenStore(tokenStore);//令牌存储策略
// 令牌增强,支持Jwt令牌
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
// 注意顺序accessTokenConverter在最后
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
service.setTokenEnhancer(tokenEnhancerChain);
service.setReuseRefreshToken(false); // 只对"存储"的有效,jwt_stoken无效
service.setAccessTokenValiditySeconds(60); // 令牌默认有效期2小时
service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
return service;
}
@Bean
public AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder) {
List<AuthenticationProvider> providers = new ArrayList<>();
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService(passwordEncoder));
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
providers.add(daoAuthenticationProvider);
return new ProviderManager(providers);
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Bean
public TokenEnhancer tokenEnhancer() {
return (oAuth2AccessToken, oAuth2Authentication) -> {
Map<String, Object> info = new HashMap<>();
info.put("hello", "world");
((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);
return oAuth2AccessToken;
};
}
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
//Admin Role
UserDetails theUser = User.withUsername("rick")
.password(passwordEncoder.encode("123456"))
.roles("ADMIN").build();
//User Role
UserDetails theManager = User.withUsername("john")
.password(passwordEncoder.encode("123456"))
.roles("USER").build();
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
userDetailsManager.createUser(theUser);
userDetailsManager.createUser(theManager);
return userDetailsManager;
}
}
- 资源服务配置
ResourceServer.java
@Configuration
@EnableResourceServer
public class ResourceServer extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
// 资源id
resources.resourceId("res")
.stateless(true)
.accessDeniedHandler((request, response, e) -> {
response.getWriter().write(e.getMessage());
});
}
@Override
public void configure(HttpSecurity http) throws Exception {
((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)http.authorizeRequests()
.antMatchers("/admin").hasRole("ADMIN")
.anyRequest()).authenticated();
}
}
- 接口
IndexController.java
@RestController
public class IndexController {
@GetMapping
public String index() {
return "index";
}
@GetMapping("admin")
public String admin(Authentication authentication, @RequestParam("access_token") String accessToken) {
// 解析token
Jwt jwt = JwtHelper.decode(accessToken);
return jwt.getClaims();
}
}
测试
- Postman请求
http://localhost:8080/oauth/token \
-H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
-F grant_type=password \
-F client_id=c \
-F client_secret=123456 \
-F scope=all \
-F username=rick \
-F password=123456
被 org.springframework.security.oauth2.provider.endpoint.TokenEndpoint
处理请求。
- 响应返回token
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzIl0sInVzZXJfbmFtZSI6InJpY2siLCJzY29wZSI6WyJhbGwiXSwiaGVsbG8iOiJ3b3JsZCIsImV4cCI6MTYzNDIxOTM1NywiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiIxN1Rfb3FXZ3BKMWdzVFQ5UEhDS3lnU0p2NXMiLCJjbGllbnRfaWQiOiJjIn0.Cx5OzE2tQKcV5Mmkrbz_WTt76Mz8ABb9VbI8IwL5wiI",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzIl0sInVzZXJfbmFtZSI6InJpY2siLCJzY29wZSI6WyJhbGwiXSwiYXRpIjoiMTdUX29xV2dwSjFnc1RUOVBIQ0t5Z1NKdjVzIiwiaGVsbG8iOiJ3b3JsZCIsImV4cCI6MTYzNDQ3ODQ5NywiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJXZi1LYzZ0bXZVei1DUHVHT2xkX2lXVFJPaXMiLCJjbGllbnRfaWQiOiJjIn0.lcA7LV6Rm64Y5Vo8T-Et4be3xgIlHmZVHFbUCLL_Tz4",
"expires_in": 59,
"scope": "all",
"hello": "world",
"jti": "17T_oqWgpJ1gsTT9PHCKygSJv5s"
}
- 请求受保护资源
header中添加参数 Authorization
curl -X GET \
http://localhost:8080/ \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzIl0sInVzZXJfbmFtZSI6InJpY2siLCJzY29wZSI6WyJhbGwiXSwiaGVsbG8iOiJ3b3JsZCIsImV4cCI6MTYzNDIxOTM1NywiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiIxN1Rfb3FXZ3BKMWdzVFQ5UEhDS3lnU0p2NXMiLCJjbGllbnRfaWQiOiJjIn0.Cx5OzE2tQKcV5Mmkrbz_WTt76Mz8ABb9VbI8IwL5wiI'
请求参数添加 access_token
curl -X GET \
'http://localhost:8080/?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzIl0sInVzZXJfbmFtZSI6InJpY2siLCJzY29wZSI6WyJhbGwiXSwiaGVsbG8iOiJ3b3JsZCIsImV4cCI6MTYzNDIxOTM1NywiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiIxN1Rfb3FXZ3BKMWdzVFQ5UEhDS3lnU0p2NXMiLCJjbGllbnRfaWQiOiJjIn0.Cx5OzE2tQKcV5Mmkrbz_WTt76Mz8ABb9VbI8IwL5wiI'