使用Redis作为Spring Security OAuth2的token存储

写在前边

本文对Spring Security OAuth2的token使用Redis保存,相比JWT实现的token存储,Redis可以随时吊销access_token,并且Redis响应速度很快,没有加密解密的过程

本文源代码在redis-token-saved模块中,仓库地址:https://github.com/hellxz/spring-security-oauth2-learn

这里就不做测试了,仅做简记

代码层级

Maven依赖

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
	<dependencies>
        
		<!-- Spring Security OAuth2 -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.4.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
	</dependencies>

授权服务器保存token到Redis

application.yml

server:
  port: 8080

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123
    database: 0

AuthorizationConfig

@Configuration
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * redis工厂,默认使用lettue
     */
    @Autowired
    public RedisConnectionFactory redisConnectionFactory;

    /**
     * 用户认证管理器
     */
    @Autowired
    public AuthenticationManager authenticationManager;

    /**
     * 用户服务
     */
    @Autowired
    public UserDetailsService userDetailsService;

    /**
     * 密码加密器
     */
    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 授权服务安全配置,主要用于放行客户端访问授权服务接口
     *
     * @param security AuthorizationServerSecurityConfigurer
     * @throws Exception 异常
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //允许客户端表单提交
        security.allowFormAuthenticationForClients()
                //客户端校验token访问许可
                .checkTokenAccess("permitAll()")
                //客户端token调用许可
                .tokenKeyAccess("permitAll()");
    }

    /**
     * 客户端信息配置,可配置多个客户端,这里可以使用配置文件进行代替
     *
     * @param clients 客户端设置
     * @throws Exception 异常
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client-a")
                .secret(passwordEncoder.encode("client-a-secret"))
                .redirectUris("http://localhost:9001/callback")
                //支持 授权码、密码两种授权模式,支持刷新token功能
                .authorizedGrantTypes("authorization_code", "password", "refresh_token");
    }

    /**
     * 配置端点
     *
     * @param endpoints 端点
     * @throws Exception 异常
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //配置认证管理器
        endpoints.authenticationManager(authenticationManager)
                //配置用户服务
                .userDetailsService(userDetailsService)
                //配置token存储的服务与位置
                .tokenServices(tokenService())
                .tokenStore(tokenStore());
    }

    @Bean
    public TokenStore tokenStore() {
        //使用redis存储token
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        //设置redis token存储中的前缀
        redisTokenStore.setPrefix("auth-token:");
        return redisTokenStore;
    }

    @Bean
    public DefaultTokenServices tokenService() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        //配置token存储
        tokenServices.setTokenStore(tokenStore());
        //开启支持refresh_token,此处如果之前没有配置,启动服务后再配置重启服务,可能会导致不返回token的问题,解决方式:清除redis对应token存储
        tokenServices.setSupportRefreshToken(true);
        //复用refresh_token
        tokenServices.setReuseRefreshToken(true);
        //token有效期,设置12小时
        tokenServices.setAccessTokenValiditySeconds(12 * 60 * 60);
        //refresh_token有效期,设置一周
        tokenServices.setRefreshTokenValiditySeconds(7 * 24 * 60 * 60);
        return tokenServices;
    }
}

SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置认证管理器信息,这里可以使用UserDetailsService实现类来提供用户信息,或Provider+UserDetailsService
     *
     * @param auth 认证管理器配置
     * @throws Exception 异常
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //@formatter:off
        auth.inMemoryAuthentication()
                .withUser("hellxz")
                .password(passwordEncoder().encode("test"))
                .authorities(AuthorityUtils.createAuthorityList("all"));
        //@formatter:on
    }

    /**
     * 配置http访问控制
     *
     * @param http http安全配置
     * @throws Exception 异常
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //放行options方法请求
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable();
    }

    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return super.userDetailsService();
    }
}

启动类

/**
 * redis作为token存储的授权server
 */
@SpringBootApplication
public class RedisAuthorizationServer {

    public static void main(String[] args) {
        SpringApplication.run(RedisAuthorizationServer.class, args);
    }
}

资源服务器访问Redis校验token配置

application.yml

server:
  port: 8081

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123
    database: 0

ResourceServerConfig

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        //无状态
        resources.stateless(true);
        //设置token存储
        resources.tokenStore(tokenStore());
    }

    /**
     * 设置token存储,这一点配置要与授权服务器相一致
     */
    @Bean
    public RedisTokenStore tokenStore(){
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        redisTokenStore.setPrefix("auth-token:");
        return redisTokenStore;
    }
}

SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .httpBasic();
    }
}

ResourceController

@RestController
public class ResourceController {
    private static final Logger log = LoggerFactory.getLogger(ResourceController.class);

    @GetMapping("/user/{username}")
    public UserVO user(@PathVariable String username){
        log.info("{}", SecurityContextHolder.getContext().getAuthentication());
        return new UserVO(username, username + "@foxmail.com");
    }
}

UserVO

package com.github.hellxz.oauth2.web.vo;

public class UserVO {
    private String username;
    private String email;

    public UserVO(String username, String email) {
        this.username = username;
        this.email = email;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

启动类

/**
 * 使用redis作为token存储的资源服务器,这里不使用调用授权服务器的方式去校验资源,只需要从redis中取token进行判断即可
 */
@SpringBootApplication
public class RedisResourceServer {
    public static void main(String[] args) {
        SpringApplication.run(RedisResourceServer.class, args);
    }
}
posted @ 2019-12-15 16:11  东北小狐狸  阅读(17965)  评论(1编辑  收藏  举报