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数据,这里就结束了。

     
  • 相关阅读:
    阅读笔记:管理学
    Vs2010中文版MSDN 安装方法
    .NET 产品版权保护方案 (.NET源码加密保护)
    WPF 判断是否为设计(Design)状态
    重写成员时违反了继承安全性规则。重写方法的安全可访问性必须与所重写方法的安全可访问性匹配。
    没有为此解决方案配置选中要生成的项目 .
    何崚谈阿里巴巴前端性能优化最佳实践
    Oracle10GODP连接11G数据库,出现ORA 1017用户名/口令无效; 登录被拒绝 的问题
    HTTP、TCP、UDP、Socket (转)
    编译的时候生成.g.cs还有.g.i.cs,有什么区别?
  • 原文地址:https://www.cnblogs.com/adao21/p/Securitycloud.html
Copyright © 2011-2022 走看看