zoukankan      html  css  js  c++  java
  • Spring Security OAuth2使用Redis作为token存储

     

    Spring Security OAuth2使用Redis作为token存储

    授权application.yml 服务器保存token到Redis

    server:
      port: 8080
    
    spring:
      redis:
        host: 127.0.0.1
        port: 6379
        password: 123
        database: 0 #也可以在代码中指定

    Maven依赖

          <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.62</version>
            </dependency>

    在spring security oauth2中,授权服务使用redis存储token的时候,报错:

    java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V

    这说明版本有问题,解决方案是,将oauth2的版本升级到2.3.3,即在pom文件中,加入:

    <!-- oauth2 start -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth</groupId>
        <artifactId>spring-security-oauth2</artifactId>
        <!-- 指明版本,解决redis存储出现的问题:java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V问题 -->
        <version>2.3.3.RELEASE</version>
    </dependency>
    <!-- oauth2 end -->

    代码分为认证端和客户端两个服务

    认证端,也就是我的security服务

    有两个文件,一个配置问津,一个

    1.AuthResourcesConfig
    
    
    package com.adao.security.config;

    import com.adao.security.common.AuthResourcesConfig;
    import com.adao.security.service.SSOUserDetailsService;
    import lombok.extern.log4j.Log4j2;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    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.token.DefaultTokenServices;
    import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

    /**
    * @author * @version 1.0 * @date 2021/8/12 * @Description: 资源服务信息公共配置类 */ public class AuthResourcesConfig { /** * token过期时间,单位为秒 */ public static final int TOKEN_SECONDS_ACCESS = 10 * 60; /** * 刷新token时间,单位为秒 */ public static final int TOKEN_SECONDS_REFRESH = 10 * 60; /** * 资源服务客户端ID信息 */ //rtp public static final String CLIENT_RTP = "RTP"; //manage public static final String CLIENT_RTP_MANAGE = "adao-rtp-manage"; /** * 资源节点对应的密钥,目前统一为 adao */ public static final String CLIENT_RTP_SECRET = "adao"; }

    2.AuthorizationServerConfig

    package com.adao.security.config;
    
    import com.adao.security.common.AuthResourcesConfig;
    import com.adao.security.service.SSOUserDetailsService;
    import lombok.extern.log4j.Log4j2;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    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.token.DefaultTokenServices;
    import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
    
    
    /**
     * @author adao
     * @version 1.0
     * @Description: 认证服务器配置
     * @date 2021/8/11
     */
    @Configuration
    @EnableAuthorizationServer
    @Log4j2
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        /**
         * 鉴权模式
         */
        public static final String GRANT_TYPE[] = {"password", "refresh_token"};
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        /**
         * 用户服务
         */
        @Autowired
        public SSOUserDetailsService userDetailsService;
    
        @Autowired
        private RedisConnectionFactory redisConnectionFactory;
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        /**
         * Redis数据库存
         */
        public static final int REDIS_CONNECTION_DATABASE = 14;
    
        /**
         * 客户端信息配置,可配置多个客户端,可以使用配置文件进行代替
         *
         * @param clients 客户端设置
         * @throws Exception 异常
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // 定义客户端应用的通行证
            clients.inMemory()
                    // rtp-manage
                    .withClient(AuthResourcesConfig.CLIENT_RTP_MANAGE)
                    .secret(new BCryptPasswordEncoder().encode(AuthResourcesConfig.CLIENT_RTP_SECRET))
                    .authorizedGrantTypes(GRANT_TYPE[0], GRANT_TYPE[1])
                    .scopes("all")
                    .autoApprove(true)
    
                    // rtp
                    .and()
                    .withClient(AuthResourcesConfig.CLIENT_RTP)
                    .secret(new BCryptPasswordEncoder().encode(AuthResourcesConfig.CLIENT_RTP_SECRET))
                    .authorizedGrantTypes(GRANT_TYPE[0], GRANT_TYPE[1])
                    .scopes("all")
                    .autoApprove(true);
    
        }
    
        /**
         * 配置端点
         *
         * @param endpoints 端点
         * @throws Exception 异常
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            //配置认证管理器
            endpoints.authenticationManager(authenticationManager)
                    //配置用户服务
                    .userDetailsService(userDetailsService)
                    //配置token存储的服务与位置
                    .tokenServices(tokenService())
                    .tokenStore(redisTokenStore());
        }
    
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) {
            //允许用户访问OAuth2 授权接口
            security.allowFormAuthenticationForClients();
            //允许已授权用户访问 checkToken 接口和获取 token 接口
            security.tokenKeyAccess("permitAll()");
            //允许已授权用户获取 token 接口
            security.checkTokenAccess("permitAll()");
        }
    
        /**
         * 设置token存储,资源服务器配置与此处相一致
         */
        @Bean
        public RedisTokenStore redisTokenStore() {
            // 指定redis数据库存储token,与业务库区分。
            LettuceConnectionFactory lettuceConnectionFactory = (LettuceConnectionFactory) redisTemplate.getConnectionFactory();
            lettuceConnectionFactory.setDatabase(REDIS_CONNECTION_DATABASE);
            RedisTokenStore redisTokenStore = new RedisTokenStore(lettuceConnectionFactory);
    // 也可以用在何种方式,这样用哪个数据库是在配置文件中指定    
    //RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
    log.info("Oauth2 redis database : [{}]", lettuceConnectionFactory.getDatabase()); //设置redis token存储中的前缀 redisTokenStore.setPrefix("auth-token:"); return redisTokenStore; } @Bean public DefaultTokenServices tokenService() { DefaultTokenServices tokenServices = new DefaultTokenServices(); //配置token存储  tokenServices.setTokenStore(redisTokenStore()); //开启支持refresh_token tokenServices.setSupportRefreshToken(true); //复用refresh_token tokenServices.setReuseRefreshToken(true); //token有效期  tokenServices.setAccessTokenValiditySeconds(AuthResourcesConfig.TOKEN_SECONDS_ACCESS); //refresh_token有效期  tokenServices.setRefreshTokenValiditySeconds(AuthResourcesConfig.TOKEN_SECONDS_REFRESH); return tokenServices; } }

    接下来是资源端

    1.ResourceServerConfig

    package com.adao.manage.config;
    
    import com.adao.manage.common.AuthExceptionHandler;
    import lombok.extern.log4j.Log4j2;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    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.oauth2.provider.token.store.redis.RedisTokenStore;
    
    /**
     * @author adao
     * @version 1.0
     * @date 2021/8/11
     * @description 资源服务配置类
     */
    @Configuration
    @EnableResourceServer
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    @Log4j2
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
        @Autowired
        private RedisConnectionFactory redisConnectionFactory;
    
        @Autowired
        private AuthExceptionHandler authExceptionHandler;
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        /**
         * Redis数据库存
         */
        public static final int REDIS_CONNECTION_DATABASE = 14;
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            //状态
            resources.stateless(true)
                    .accessDeniedHandler(authExceptionHandler)
                    .authenticationEntryPoint(authExceptionHandler);
            //设置token存储
            resources.tokenStore(redisTokenStore());
        }
    
        /**
         * 设置token存储,这一点配置要与授权服务器相一致
         */
        @Bean
        public RedisTokenStore redisTokenStore() {
    
            // 指定redis数据库存储token,与业务库区分。
            LettuceConnectionFactory lettuceConnectionFactory = (LettuceConnectionFactory) redisTemplate.getConnectionFactory();
            lettuceConnectionFactory.setDatabase(REDIS_CONNECTION_DATABASE);
            RedisTokenStore redisTokenStore = new RedisTokenStore(lettuceConnectionFactory);
            log.info("Oauth2 redis database : [{}]",lettuceConnectionFactory.getDatabase());
    
            //设置redis token存储中的前缀
            redisTokenStore.setPrefix("auth-token:");
            return redisTokenStore;
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            //请求权限配置
            http.authorizeRequests()
                    //下边的路径放行,不需要经过认证
                    .antMatchers("/actuator/health").permitAll()
                    .antMatchers("/v2/api-docs", "/swagger-resources/configuration/ui",
                            "/swagger-resources", "/swagger-resources/configuration/security",
                            "/swagger-ui.html", "/webjars/**").permitAll()
                    //其余接口没有角色限制,但需要经过认证,只要携带token就可以放行
                    .antMatchers("/dictionary/**").permitAll()
                    .anyRequest()
                    .authenticated();
        }
    }
    AuthExceptionHandler   异常捕获处理类
    package com.adao.manage.common;
    
    import com.alibaba.fastjson.JSON;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.security.web.access.AccessDeniedHandler;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * @author adao
     * @version 1.0
     * @date 2021/8/11
     * @Description: 权限不足返回信息处理类
     */
    @Component
    @Slf4j
    public class AuthExceptionHandler implements AuthenticationEntryPoint, AccessDeniedHandler {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    
            Throwable cause = authException.getCause();
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            // CORS "pre-flight" request
            response.addHeader("Access-Control-Allow-Origin", "*");
            response.addHeader("Cache-Control", "no-cache");
            response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
            response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
            response.addHeader("Access-Control-Max-Age", "1800");
            if (cause instanceof InvalidTokenException) {
                log.error("InvalidTokenException : {}", cause.getMessage());
                //Token无效
                response.getWriter().write(JSON.toJSONString(ApiResult.fail(ApiCode.ACCESS_TOKEN_INVALID)));
            } else {
                log.error("AuthenticationException : NoAuthentication");
                //资源未授权
                response.getWriter().write(JSON.toJSONString(ApiResult.fail(ApiCode.ACCESS_UNAUTHORIZED)));
            }
        }
    
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.addHeader("Access-Control-Allow-Origin", "*");
            response.addHeader("Cache-Control", "no-cache");
            response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
            response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
            response.addHeader("Access-Control-Max-Age", "1800");
            //访问资源的用户权限不足
            log.error("AccessDeniedException : {}", accessDeniedException.getMessage());
            response.getWriter().write(JSON.toJSONString(ApiResult.fail(ApiCode.INSUFFICIENT_PERMISSIONS)));
        }
    }

    公用的范围code及 含义

    package com.adao.manage.common;
    
    
    public enum ApiCode {
    
        SUCCESS(200, "操作成功"),
    
        /**
         * 表示接口调用方异常提示
         */
        ACCESS_TOKEN_INVALID(1001, "access_token 无效"),
        REFRESH_TOKEN_INVALID(1002, "refresh_token 无效"),
        INSUFFICIENT_PERMISSIONS(1003, "该用户权限不足以访问该资源接口"),
        ACCESS_UNAUTHORIZED(1004, "访问此资源需要身份验证"),
    
       
    }

    最后开始测试

    postman 设置

    http://192.168.10.90:6005/oauth/token?grant_type=password&username=zq&password=123456&scope=all

     

     直接结果可以看到,获取到了token数据,这里就结束了。

     
  • 相关阅读:
    light oj 1105 规律
    light oj 1071 dp(吃金币升级版)
    light oj 1084 线性dp
    light oj 1079 01背包
    light oj 1068 数位dp
    light oj 1219 树上贪心
    light oj 1057 状压dp TSP
    light oj 1037 状压dp
    矩阵快速幂3 k*n铺方格
    矩阵快速幂2 3*n铺方格
  • 原文地址:https://www.cnblogs.com/adao21/p/Securitycloud.html
Copyright © 2011-2022 走看看