前言
搭建Spring Security OAuth2授权码模式demo,一方面在慢慢完善该文过程中做一个系统的总结,一方面做一个知识输出。
一、授权服务器搭建
- 主要依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- SecurityConfig
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private LoginFailureHandler failureHandler;
@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**/*.js",
"/**/*.css",
"/toRegister",
"/logOut",
"/login"
)
.anonymous()
.anyRequest().authenticated() //所有请求都需要通过认证
.and().cors()
.and()
// .httpBasic() //Basic提交
.formLogin()
.loginPage("/login")
.permitAll()
// 登录失败
.failureHandler(failureHandler)
//关跨域保护
.and()
.csrf().disable();
}
}
AuthorizationConfig
@Configuration
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter
{
@Autowired
private DataSource dataSource;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* 配置令牌端点(Token Endpoint)的安全约束
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
{
//允许表单提交
oauthServer.allowFormAuthenticationForClients()
.checkTokenAccess("isAuthenticated()");
}
/**
* 客户端配置
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception
{
RedisClientDetailsService clientDetailsService = new RedisClientDetailsService(dataSource);
clientDetailsService.setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT);
clientDetailsService.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT);
clients.withClientDetails(clientDetailsService);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
// .exceptionTranslator(new CustomWebResponseExceptionTranslator())
.tokenStore(tokenStore())
.tokenServices(defaultTokenServices());
}
/**
* 基于 Redis 实现,令牌保存到缓存
*/
@Bean
public TokenStore tokenStore()
{
RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
tokenStore.setPrefix(CacheConstants.OAUTH_ACCESS);
return tokenStore;
}
/**
* 生成token的处理
*/
@Primary
@Bean
public DefaultTokenServices defaultTokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
// 是否支持 refreshToken
tokenServices.setSupportRefreshToken(true);
// 是否复用 refreshToken
tokenServices.setReuseRefreshToken(true);
// tokenServices.setTokenEnhancer(tokenEnhancer());
// token有效期自定义设置,默认12小时,这里修改
tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24 * 15);
//默认30天,这里修改
tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
return tokenServices;
}
}
二、资源服务器搭建
ResourceServerConfig
@Configuration
@EnableResourceServer
@Order(0)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter
{
@Autowired
private CustomAccessDeniedHandler accessDeniedHandler;
@Autowired
private CustomAuthExceptionEntryPoint exceptionEntryPoint;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private Environment environment;
@Autowired
private DefaultUserAuthenticationConverter defaultUserAuthenticationConverter;
@Autowired
private UserDetailsService userDetailsService;
@Primary
@Bean
public RemoteTokenServices remoteTokenServices() {
final RemoteTokenServices tokenServices = new RemoteTokenServices();
//设置授权服务器check_token端点完整地址
tokenServices.setCheckTokenEndpointUrl(environment.getProperty("security.oauth2.resource.token-info-uri"));
//设置客户端id与secret,注意:client_secret值不能使用passwordEncoder加密!
tokenServices.setClientId(environment.getProperty("security.oauth2.client.client-id"));
tokenServices.setClientSecret(environment.getProperty("security.oauth2.client.client-secret"));
//check_token后获取用户信息
defaultUserAuthenticationConverter.setUserDetailsService(userDetailsService);
DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
defaultAccessTokenConverter.setUserTokenConverter(defaultUserAuthenticationConverter);
tokenServices.setAccessTokenConverter(defaultAccessTokenConverter);
return tokenServices;
}
/**
* 设置token存储,这一点配置要与授权服务器相一致
*/
@Bean
public RedisTokenStore tokenStore(){
RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
redisTokenStore.setPrefix("oauth:access:");
return redisTokenStore;
}
@Override
public void configure(HttpSecurity http) throws Exception {
//设置创建session策略
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
//@formatter:off
//所有请求必须授权
http.authorizeRequests()
.anyRequest()
.authenticated();
//@formatter:on
}
@Override
public void configure(ResourceServerSecurityConfigurer resources)
{
resources.stateless(false);
//设置token存储
resources.tokenStore(tokenStore());
resources.resourceId("resource1");
resources.authenticationEntryPoint(exceptionEntryPoint).accessDeniedHandler(accessDeniedHandler);
resources.tokenServices(remoteTokenServices());
}
}
-
application.yml
auth-server: http://*.*.*.*:8082
security:
oauth2:
client:
client-id: client-a # 授权服务器配置的client id
client-secret: client-a-secret # 授权服务器配置的client secret
scope: read_user_info
#access-token-uri: ${auth-server}/oauth/token # 获取access token接口
#user-authorization-uri: ${auth-server}/oauth/authorize # 获取Authorization Code接口
resource:
token-info-uri: ${auth-server}/oauth/check_token # 验证token的接口
# user-info-uri: ${auth-server}/user # 一个可以获取认证授权的自定义接口,可以在授权服务器,也可以在其他服务器上
# prefer-token-info: true # 如果同时配置了token-info-uri 和 user-info-uri,这个现象设置使用哪个取验证授权
-
SysUserController
/**
* 登录验证
*
* @author demo
*/
@RestController
public class SysUserController {
@Autowired
private ISysUserService userService;
/**
* 获取用户信息
*
* @return 用户信息
*/
@GetMapping("/getInfo/{userName}")
public AjaxResult getInfo(@PathVariable(value = "userName", required = false) String userName) {
SysUser user = userService.selectUserByUserName(userName);
return AjaxResult.success(user);
}
@GetMapping("/user")
public Authentication getUser() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
三、客户端接口文档
1)获取授权码
接口url | http://AUTHSERVER/oauth/authorize |
请求方法 | GET |
输入参数 | - client_id 客户端id - redirect_uri 回调url 一定要与授权服务器配置保持一致,否则得不到授权码 - response_type code 授权码模式必须是code - scope 作用域 与授权服务器配置保持一致 - state 加密串或状态标识(可选)
|
输出结果 | 回调客户端url,传入code |
2)授权码换令牌
接口url | http://AUTHSERVER/oauth/token |
请求方法 | POST |
请求参数列表 | - code 授权码 - grant_type authorization_code - redirect_uri 回调url - scope作用域 |
请求头列表 | - Authorization:Basic 经Base64加密后的username:password的字符串
|
输出参数 | {"access_token":"c4dbd362-4354-4434-a9ba-a998eca55bcb", "token_type":"bearer","expires_in":43199,"scope":"read_user_info"} |
3)使用token访问受保护的资源
接口url | http://RESOURCESERVER/getUser/{userName}
|
请求方法 | GET |
请求头列表
| Authorization:Bearer 令牌值 这里要注意的是Bearer与token间有一个空格 |
输出参数 | 用户信息json串 |
总结
主要配置如上,后续还会继续补充细节,若有错误,期待指正,谢谢!