springcloud-oauth2实践

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

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值