springcloud-oauth2实践
引言
- 最近了解到oauth2,经过一番学习与实践后将一些经验总结下来,并将在springcloud上对oauth2的实践方案做下学习笔记
- 本文假设你了解springboot、idea、gradle的相关基本概念及基本使用
- 笔者能力有限,本文所述内容如有错误欢迎指正
本文对谁可能有帮助
- 如果你已经使用了springcloud,并且收到需求要将部分资源开放给第三方去使用,而且想采用oauth2这套协议来实现,那么本文所述内容应该对你有所帮助
实践环境
实践环境很重要,很多时候说方法不行一般都是环境、版本等不对应引起的,因此本文所述内容皆在实践环境下有效,其他环境未实践过没有发言权,本文只作为入门参考同时也是学习记录
- windows10
- IDEA 2017.2.6
- jdk1.8
- springCloudVersion = ‘Finchley.SR1’
- springBootVersion = ‘2.0.5.RELEASE’
- gradle-4.8.1-all
配置过程
- 假设你已经了解了oauth2,知道oauth2的 客户端、认证服务、资源服务等概念。
- 首先,你得把springboot工程搭建好,并引入oauth2的依赖
compile('org.springframework.cloud:spring-cloud-starter-oauth2')
配置WebSecurityConfigurerAdapter
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* Created by hwj on 2018/9/10.
*/
//只有开启了这个注解,@PreAuthorize才会起效,否则需要到ResourceServerConfigurerAdapter里配置才行
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
//此处必须声明这个bean类,否则无法注入AuthenticationManager
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.logout().permitAll()
.and()
.csrf().disable();
}
}
配置AuthorizationServerConfigurerAdapter
import com.gosuncn.ics.security.service.ClientDetailsServiceImpl;
import com.gosuncn.ics.security.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
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.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 认证服务器
* Created by hwj on 2018/9/10.
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
AuthenticationManager authenticationManager;
@Autowired
UserDetailsServiceImpl userDetailsService;
@Autowired
ClientDetailsServiceImpl clientDetailsService;
@Autowired
private RedisConnectionFactory connectionFactory;
@Bean
public TokenStore tokenStore() {
RedisTokenStore redis = new RedisTokenStore(connectionFactory);
return redis;
}
@Primary
@Bean
DefaultTokenServices tokenServices() {
DefaultTokenServices d = new DefaultTokenServices();
d.setClientDetailsService(clientDetailsService);//如果没有设置此项,则client设置的token有效期无效
d.setTokenStore(tokenStore());
d.setReuseRefreshToken(true);//是否重复使用refresh_token
d.setSupportRefreshToken(true);//是否支持refresh_token,为false则refresh_token无法使用
return d;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()")//对于CheckEndpoint控制器[框架自带的校验]的/oauth/token端点允许所有客户端发送器请求而不会被Spring-security拦截
.checkTokenAccess("isAuthenticated()")//要访问/oauth/check_token必须设置为permitAll(),此接口一般不对外公布,是springoauth2内部使用,因此这里要设为isAuthenticated()
.allowFormAuthenticationForClients()//允许客户表单认证,不加的话/oauth/token无法访问
.passwordEncoder(passwordEncoder);//设置oauth_client_details中的密码编码器
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenServices(tokenServices())
.authenticationManager(authenticationManager);
// endpoints.pathMapping("/oauth/token","/oauth/token3");//可以修改默认的endpoint路径
}
}
- 继承AuthorizationServerConfigurerAdapter并实现三个configure即可, 其中AuthorizationServerSecurityConfigurer主要 配置token的访问权限
ClientDetailsServiceConfigurer主要配置提供客户端信息的接口服务,主要实现ClientDetailsService接口即可
AuthorizationServerEndpointsConfigurer主要配置tokenServices,一般使用默认的DefaultTokenServices即可 - 需要注意的是本例中使用了redis来存储token,因此需要引入如下依赖
compile('org.springframework.boot:spring-boot-starter-data-redis')
- 由于springCloud本身已经提供了RedisTokenStore,因此使用redis来存储token非常方便,同时为了方便测试,springCloud还提供了InMemoryTokenStore可以暂时放在内存中,实际生产环境肯定是放在redis比较靠谱。
配置ResourceServerConfigurerAdapter
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
/**
* 资源服务器
* https://blog.csdn.net/u013825231/article/details/80556221
* Created by hwj on 2018/9/11.
*/
@EnableResourceServer
@Configuration
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
/**
* 这里对资源的权限配置可以用@EnableGlobalMethodSecurity(prePostEnabled = true)和@PreAuthorize进行代替
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
.antMatchers("/open/**").permitAll()//开放的资源不用授权
.antMatchers("/oauth/**").permitAll()//开放oauth接口
.antMatchers("/actuator/**").permitAll()//开放actuator接口
.anyRequest().authenticated()//其他任何请求都需要授权
;
}
private static final String DEMO_RESOURCE_ID = "resource1";
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(DEMO_RESOURCE_ID).stateless(true);
}
}
实现ClientDetailsService
- ClientDetailsService主要实现loadClientByClientId接口,即根据clientId获取到详细的client信息,生产环境下client信息一般都是存在数据库里,这里实现通过相关数据接口获取到client信息然后包装成ClientDetails 再返回。
- 另外,ClientDetails 只是一个接口,系统有一个实现类org.springframework.security.oauth2.provider.client.BaseClientDetails可供使用
- 此类请根据实际情况实现,没有通用做法,以下仅供参考。
import com.google.gson.Gson;
import com.gosuncn.ics.security.bean.ApiResult;
import com.gosuncn.ics.security.entity.OauthClient;
import com.gosuncn.ics.security.util.Log;
import org.apache.http.util.TextUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Created by hwj on 2018/9/10.
*/
@Service
public class ClientDetailsServiceImpl implements ClientDetailsService {
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
DasFeignService dasFeignService;
/**
* 注意secret需要BCrypt加密,否则会报Encoded password does not look like BCrypt
*
* @param s
* @return
* @throws ClientRegistrationException
*/
@Override
public ClientDetails loadClientByClientId(String s) throws ClientRegistrationException {
ApiResult<OauthClient> res = dasFeignService.getClientInfoByClientId(s);
if (res.getCode() != 1) {
Log.error(this,res.getMessage());
throw new ClientRegistrationException(s+"不存在");
}
OauthClient client = res.getData();
BaseClientDetails bcd = null;
bcd = new BaseClientDetails(
s
, client.getResourceIds()==null?"":client.getResourceIds()
, client.getScope()
, client.getAuthorizedGrantTypes()
, client.getAuthorities()==null?"":client.getAuthorities());
if(!TextUtils.isBlank(client.getClientSecret())){
bcd.setClientSecret(passwordEncoder.encode(client.getClientSecret()));
}
if(!TextUtils.isBlank(client.getAdditionalInformation())) {
Gson gson = new Gson();
Map<String, Object> map = new HashMap<String, Object>();
map = gson.fromJson(client.getAdditionalInformation(), map.getClass());
bcd.setAdditionalInformation(map);
}
if(!TextUtils.isBlank(client.getAutoapprove())) {
Set set=new HashSet<String>();
set.add(client.getAutoapprove());
bcd.setAutoApproveScopes(set);
}
if(!TextUtils.isBlank(client.getWebServerRedirectUri())) {
Set set=new HashSet<String>();
set.add(client.getWebServerRedirectUri());
bcd.setRegisteredRedirectUri(set);
}
if(client.getRefreshTokenValidity()!=null){
bcd.setRefreshTokenValiditySeconds(client.getRefreshTokenValidity());
}
if(client.getAccessTokenValidity()!=null){
bcd.setAccessTokenValiditySeconds(client.getAccessTokenValidity());
}
return bcd;
}
}
实现UserDetailsService
- UserDetailsService主要实现loadUserByUsername接口,即根据username获取到详细的用户权限信息,生产环境下用户信息一般都是存在数据库里,这里实现通过相关数据接口获取到用户信息然后包装成UserDetails 再返回。
- 另外,UserDetails只是一个接口,系统有一个实现类org.springframework.security.core.userdetails.User可供使用
- 此类请根据实际情况实现,没有通用做法,以下仅供参考。
import com.gosuncn.ics.security.bean.ApiResult;
import com.gosuncn.ics.security.entity.Right;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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 org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
/**
* Created by hwj on 2018/9/10.
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService{
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
DasFeignService dasFeignService;
/**
* 注意password需要BCrypt加密,否则会报Encoded password does not look like BCrypt
* 授权的时候是对角色授权,而认证的时候应该基于资源,而不是角色,因为资源是不变的,而用户的角色是会变的
* 因此这里授予的是用户的资源权限而非角色(角色是变化的,而系统的资源是固定的)
* @param s
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
ApiResult<com.gosuncn.ics.security.entity.User> res= dasFeignService.getUserByAccount(s);
if(res.getCode()!=1){
throw new UsernameNotFoundException(res.getCode()==99?res.getMessage():"账号不存在");
}
Collection<GrantedAuthority> authorities = new HashSet<>();
com.gosuncn.ics.security.entity.User userEntity=res.getData();
ApiResult<List<Right>> result;
if("sysadmin".equals(userEntity.getAccount())){//系统管理员添加所有权限
result=dasFeignService.getAllRights();
}else{
result=dasFeignService.getRightsByUserId(userEntity.getUserId());
}
if(result.getCode()==1){
for(Right right:result.getData()){
authorities.add(new SimpleGrantedAuthority(right.getRightCode()));
}
}
User user = new User(userEntity.getAccount(),passwordEncoder.encode( userEntity.getPassword()),authorities);
return user;
}
}
接口说明
- 经过上面三个大类的配置,基本oauth2就可以使用了,下面对几个endpoint的使用作说明
- 请求接口:/oauth/token
- 请求方式:post
- 作用说明:获取access_token
参数 | 说明 |
---|---|
grant_type | 必传,此字段接受 password:此种类型需要client_id,client_secret,username,password必传 authorization_code:此种类型需要client_id,client_secret,code必传 refresh_token:此种类型需要client_id,client_secret,refresh_token必传 client_credentials:此种类型需要client_id,client_secret必传 |
client_id | 必传 ,客户端id |
client_secret | 非必传 ,密钥 |
username | 非必传 ,用户名 |
password | 非必传,用户密码 |
scope | 非必传,不传默认全部 |
code | 非必传,授权回调过来的code |
refresh_token | 非必传,用来刷新access_token,刷新后旧的access_token不可用 |
- 返回结果示例:
//200
{
"access_token": "e8629c51-bff5-4eaa-955f-191faf1b2819",
"token_type": "bearer",
"refresh_token": "55868cf7-11d9-4a00-88f4-ad0b349a9e99",
"expires_in": 19999,
"scope": "all read"
}
//400:当username或password或refresh_token或grant_type错误时返回400
//401:当client_id或client_secret错误时返回401
{
"error": "invalid_client",
"error_description": "Bad client credentials"
}
- 请求接口:/oauth/authorize
- 请求方式:get
- 作用说明:获取授权码code
参数 | 说明 |
---|---|
client_id | 必传,客户端id |
response_type | 必传,固定值"code" |
redirect | 非必传, 重定向地址,授权成功后会在此地址后带上code参数,拿到此code后再去获取accesstoken; 如果在ClientDetailsService中配置了重定向地址则此字段可以不传,传的话要保证与ClientDetailsService设置的一致 |
- 请求接口:/oauth/check_token
- 请求方式:get
- 作用说明:验证access_token是否有效
参数 | 说明 |
---|---|
token | 必传,accesstoken |
- 返回结果示例:
{
"active": true,
"exp": 1539107867,
"user_name": "hwj",
"authorities": [
"user:list"
],
"client_id": "ics-web",
"scope": [
"all"
]
}
问答
问: 如果你使用了redis来存储token,那么可能会遇到redis在存储时产生的异常
- 答:这是由于依赖的spring-security-oauth2是2.2的版本,与2.3版的接口产生了差异,因此在依赖文件中加入以下依赖即可
compile('org.springframework.security.oauth:spring-security-oauth2:2.3.3.RELEASE')
问:在ClientDetailsService接口中的loadClientByClientId设置客户端的token有效期不起作用怎么办?
- 答:如果你使用DefaultTokenServices,那么一定要在此对象中调用setClientDetailsService接口将ClientDetailsService实例对象设置进来,这样客户端自定义的token有效期才会起作用
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {
...
@Primary
@Bean
DefaultTokenServices tokenServices() {
DefaultTokenServices d = new DefaultTokenServices();
d.setClientDetailsService(clientDetailsService);//如果没有设置此项,则client设置的token有效期无效
d.setTokenStore(tokenStore());
d.setReuseRefreshToken(true);
d.setSupportRefreshToken(true);
return d;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenServices(tokenServices())
.authenticationManager(authenticationManager);
}
...
}
问:如果认证服务和资源服务分开成两个独立服务,那么应该如何配置?
- 答:一般情况下认证服务和资源服务可以在同一个服务中,这种方式比较简单,如果分离,那么在资源服务中就要使用RemoteTokenServices或者在资源服务的配置文件中配置
通过配置文件application.yml方式(推荐)
security:
oauth2:
resource:
user-info-uri: http://localhost:7601/user
- 上面的http://localhost:7601/user即为认证服务器提供的获取用户凭证的接口,此接口需要用户手动在认证服务实现,如下
/**
* 获取用户凭证(供客户端使用)
* @param principal
* @return
*/
@GetMapping("/user")
public Principal user(Principal principal){
return principal;
}
通过RemoteTokenServices方式
@EnableResourceServer
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
@Primary
@Bean
public RemoteTokenServices tokenServices() {
final RemoteTokenServices tokenService = new RemoteTokenServices();
//认证服务器的check_token endpoint
tokenService.setCheckTokenEndpointUrl("http://localhost:7601/oauth/check_token");
//填写任一有效的clientId和clientSecret即可
tokenService.setClientId("ics-web");
tokenService.setClientSecret("e10adc3949ba59abbe56e057f20f883e");
return tokenService;
}
...
}
问:资源服务提供的对外接口如何进行鉴权?
- 答:在资源配置服务中可以通过使用antMatchers来匹配接口路径并进行鉴权,或者通过注解@EnableGlobalMethodSecurity(prePostEnabled = true)
通过antMatchers示例
@EnableResourceServer
@Configuration
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
...
@Override
public void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
.antMatchers("/open/**").permitAll()//开放的资源不用授权
.antMatchers("/das/api/**").hasAuthority("get")//只有get权限的才能访问此接口,否则直接403
.anyRequest().authenticated()//其他任何请求都需要授权
;
}
...
}
通过@PreAuthorize示例
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {}
@PreAuthorize("hasRole('ADMIN') AND hasRole('DBA')")
@GetMapping("/test")
public String deleteUser(@RequestParam int id){}
问:获取到access_token后如何注销?
- 答:利用ConsumerTokenServices.revokeToken()接口,如下示例:
@RestController
public class AuthorizeController {
@Autowired
ConsumerTokenServices consumerTokenServices;
/**
* 注销accessToken,注销accessToken后refreshToken也会被清掉
*
* @param accessToken
* @return
*/
@GetMapping("/oauth/revoke_token")
public ApiResult revokeToken(
Principal principal
, @RequestParam String accessToken) {
if (consumerTokenServices.revokeToken(accessToken)) {
return ApiResultUtil.success("注销成功,accessToken已失效");
}
return ApiResultUtil.failed("注销失败,accessToken不存在");
}
}
问:如何知道access_token对应的账号?
- 答:利用ResourceServerTokenServices.loadAuthentication()接口,如下示例:
@RestController
public class AuthorizeController {
@Autowired
ResourceServerTokenServices resourceServerTokenServices;
/**
* 获取accesstoken对应的账号
*
* @param accessToken
* @return
*/
@GetMapping("/oauth/account")
public ApiResult user(@RequestParam String accessToken) {
OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(accessToken);
if (token == null) {
//throw new InvalidTokenException("Token was not recognised");
return ApiResultUtil.failed("accessToken不存在");
}
if (token.isExpired()) {
//throw new InvalidTokenException("Token has expired");
return ApiResultUtil.failed("accessToken已过期");
}
OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());
//请注意,authentication.getUserAuthentication()可能为null,因此以下方法请勿使用
// String username = ((Map<String, String>) authentication.getUserAuthentication().getDetails()).get("username");
// ((Map<String,String>)authentication.getUserAuthentication().getDetails()).get("client_id");
// ((Map<String,String>)authentication.getUserAuthentication().getDetails()).get("grant_type");
//((Map<String,String>)authentication.getUserAuthentication().getDetails()).get("client_secret");
String username =authentication.getName();
return ApiResultUtil.success("成功", username);
}
}
问:/oauth/token接口对于同一客户端同一用户获取的access_token都是一样的,如果想每次认证都获取新的access_token,那该怎么办?
- 答:使用DefaultTokenServices的话默认对于同一客户端同一用户的access_token都是一样的,直到access_token过期失效才会生成新的access_token,如果你需要每次都生成新的access_token,那么只能自定义实现AuthorizationServerTokenServices接口,这里由于已经有DefaultTokenServices实现类,我们发现,这个类有reuseRefreshToken的设置,但就是没有reuseAccessToken的设置,那么通过改造它也能实现我们要的功能。
- 注意:每次认证都生成新的access_token这种方式要做好防护机制,否则有人恶意重复认证的话那么存储access_token的服务就会被挤爆。
import org.springframework.transaction.annotation.Transactional;
...
/**
* 此类由于使用Transactional注解,因此需要spring-tx jar包(此jar包一般包含在数据库相关依赖里,由于我使用了spring-boot-starter-data-redis,里面包含了spring-tx,故不会报找不到的错误)
*/
public class CustomTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
ConsumerTokenServices, InitializingBean {
...
private boolean reuseAccessToken=true;//同一用户是否复用access_token
...
@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
...
//除了增加reuseAccessToken这个条件,其他代码保持不变
if (existingAccessToken != null&&reuseAccessToken) {
...
}
...
return accessToken;
}
public void setReuseAccessToken(boolean reuseAccessToken) {
this.reuseAccessToken = reuseAccessToken;
}
...
}
- 只需三步就可以改造成功了:1 新增reuseAccessToken变量 2 开放设置接口setReuseAccessToken 3 将原来的
if (existingAccessToken != null)变成 if (existingAccessToken != null&&reuseAccessToken) 这样我们要的功能就实现了 - 在AuthorizationServerConfigurerAdapter里将DefaultTokenServices替换为CustomTokenServices并setReuseAccessToken(false)即可
- 有其他需求都可以通过这种方式进行自定义
总结
- 个人感觉使用springcloud-oauth2这一套真要到实际项目还是非常困难的,实际项目需求什么的变化比较大,而springcloud-oauth2除非对内部实现比较了解,否则很难驾驭,想要自定义会发现比较困难或修改起来特别不方便,网上的例子大多都是简单示例,没有考虑到实际生产环境的诸多问题,如果你使用了这一套框架,那么就请做好踩坑的准备吧,个人理解不建议用这一套来实现认证和授权,还是自己设计认证和授权,灵活性和可控性都大一些。
demo源码
https://github.com/web-coding-well/SpringCloud-OAuth2
参考
理解OAuth 2.0 - 阮一峰的网络日志
http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html