美文网首页SpringBoot之路Spring Cloud程序员
Spring-Security-OAuth2资源服务器及Spri

Spring-Security-OAuth2资源服务器及Spri

作者: 彳亍路 | 来源:发表于2017-07-29 11:20 被阅读311次

    遇到的问题

    1、UserDetailService is required!
    2、使用RefreshToken时,在UserDetailService接口的public UserDetails loadUserByUsername(String username)方法中发现username为null值。
    先谈怎么出现的?
    业务场景需要,使用获取到的AccessToken中的RefreshToken去重新获取新的AccessToken对象,也就是说撤销旧的AccessToken值,创建新的AccessToken值。比如在单点登陆SSO的情况下,这个是必须的。
    之前的Demo中,可以使用password和client两种模式获取到AccessToken值,当我使用refresh_token模式时却发现返回错误提示:UserDetailService is required!

    问题1,解决方式:注入自定义的UserDetailService的对象,如下:

    @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints)throws Exception {
            endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore).userDetailsService(userDetailsService);
        }
    

    解决这个之后,发现出现了问题2,经过debug发现,当生成AccessToken的时候,会将OAuth2Authentication认证对象序列化后存入缓存中,进行保存[以RefreshToken的值为key],当使用RefreshToken的时候,会先使用RefreshToken的值为key读取字节流并反序列化成为OAuth2Authentication对象。而问题就出现在这里,因为我的Member对象未实现序列化接口,存储的时候默认将其全部序列化成字节进入Redis中,而因为未实现序列化,所以在反序列化的时候,导致Member对象属性值均为null,进而导致外层的loadUserByUsername方法参数值为null。

    问题2,解决方案:实现序列化接口即可


    Redis缓存数据结构记录概要

    获取AccessToken与刷新RefreshToken等操作都会在Redis中生成相关的数据,下面便是对相关数据结构进行简要介绍:

        private static final String ACCESS = "access:";
        1、access:${tokenValue}} 为key,存放 OAuth2AccessToken 对象
        
        private static final String AUTH_TO_ACCESS = "auth_to_access:";
        2、access:${USERNAME+CLIENT_ID+SCOPE}} 为key,存放 OAuth2AccessToken 对象
        
        private static final String AUTH = "auth:";
        3、 auth:${tokenValue} 为key,存放 OAuth2Authentication 对象
        
        private static final String REFRESH_AUTH = "refresh_auth:";
        4、 refresh_auth:${refreshTokenValue} 为key,存放 OAuth2Authentication  对象
        
        private static final String ACCESS_TO_REFRESH = "access_to_refresh:";
        5、 access_to_refresh:${tokenValue} 为key,存放 OAuth2RefreshToken 对象的value属性值
        
        private static final String REFRESH = "refresh:";
        6、 refresh:${refreshTokenValue} 为key,存放 OAuth2RefreshToken 对象
        
        
        private static final String REFRESH_TO_ACCESS = "refresh_to_access:";
        7、 refresh_to_access:${refreshTokenValue} 为key,存放 OAuth2AccessToken 对象的value属性值
        
        private static final String CLIENT_ID_TO_ACCESS = "client_id_to_access:";
        8、 refresh_to_access:${clientIdValue} 为key,存放 OAuth2AccessToken 对象
        
        private static final String UNAME_TO_ACCESS = "uname_to_access:";
        9、 refresh_to_access:${clientIdValue+":"+userNameValue} 为key,存放 OAuth2AccessToken 对象
    

    个人理解

    通过上面对于Redis缓存中的数据结构的分析,我们可以看出来,通过AccessToken和RefreshToken,我们可以针对自己的应用来做比较粗糙的权限控制,比如,通过一个AccessToken的value直接查询是否存在这个AccessToken对象是否存在,或者查询OAuthAuthentication对象是否存在,并与当前数据库中进行匹配等等,当然这只是对于权限做比较粗糙的工作,我这里也只是做一个简要的比喻。而对于权限的真正的控制,实际依赖于Spring-Security的权限注解,例如:@PreAuthorize("hasRole('ADMIN')")。在本公司中,实际做的权限控制,很尴尬,正如我前面章节所展示的只做了简单的查询校验,却并未做权限的精确控制。SpringSecurity实在是太庞大,学习成本昂贵。个人理解而言,SpringSecurityOAuth2适用于做开放平台,于本公司而言,可能是出于业务考虑(有不下三十个定制或者私有的APP连接服务器),为了便于统一管理,于是采用了OAuth2的形式,如果诸位有更好的方法,请赐教。

    OAuth2结合SpringSecurity的权限控制

    数据源的配置

    @Configuration
    public class DataStoreConfig {
        public static final String REDIS_CACHE_NAME = "redis_cache_name";//不为null即可
        public static final String REDIS_PREFIX = "redis_cache_prefix";//不为null即可
        public static final Long EXPIRE = 60 * 60L;//缓存有效时间
    
        /**
         * 配置用以存储用户认证信息的缓存
         */
        @Bean
        RedisCache redisCache(RedisTemplate redisTemplate) {
            RedisCache redisCache = new RedisCache(REDIS_CACHE_NAME, REDIS_PREFIX.getBytes(), redisTemplate, EXPIRE);
            return redisCache;
        }
    
        /**
         * 创建UserDetails存储服务的Bean:使用Redis作为缓存介质
         * UserDetails user = this.userCache.getUserFromCache(username)
         */
        @Bean
        public UserCache userCache(RedisCache redisCache) throws Exception {
            UserCache userCache = new SpringCacheBasedUserCache(redisCache);
            return userCache;
        }
    
        /**
         * 配置AccessToken的存储方式:此处使用Redis存储
         * Token的可选存储方式
         * 1、InMemoryTokenStore
         * 2、JdbcTokenStore
         * 3、JwtTokenStore
         * 4、RedisTokenStore
         * 5、JwkTokenStore
         */
        @Bean
        public TokenStore tokenStore(RedisConnectionFactory redisConnectionFactory) {
            return new RedisTokenStore(redisConnectionFactory);
        }
    }
    

    拦截器的配置

    public class Oauth2Interceptor extends HandlerInterceptorAdapter {
        @Override
        public boolean preHandle(HttpServletRequest request,
                                 HttpServletResponse response, Object handler) throws Exception {
            String accessToken = request.getParameter("access_token");
            if (StringUtils.isEmpty(accessToken)) {
                return false;
            }
            TokenStore tokenStore = (TokenStore) ApplicationSupport.getBean("tokenStore");
            OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(accessToken);
            if (oAuth2Authentication == null) {
                return false;
            }
            SecurityContextHolder.getContext().setAuthentication(oAuth2Authentication);
            return true;
        }
    }
    

    资源权限的控制

    @RestController
    @RequestMapping("/api")
    public class TestController {
        @PreAuthorize("hasRole('ADMIN')")
        @RequestMapping("/test")
        public String test() {
            return "success";
        }
    
        @PreAuthorize("hasRole('TEST')")
        @RequestMapping("/test2")
        public String test2() {
            return "success";
        }
    }
    

    1、在你的资源服务器中,使用注解@EnableResourceServer,代表你的服务是资源服务器。上篇文章,是基于自己公司所创建的资源服务器,并不算是真正意义上的资源服务器,只有使用了注解@EnableResourceServer才是真正的OAuth2的资源服务器,这样Spring的SecurityInterceptor才会对资源进行拦截并权限认证。
    2、在你的资源服务器中,创建一个与认证授权服务器中配置相同的TokenStore的Bean对象,用来查询认证信息拦截器
    3、创建一个OAuth2拦截器,并在拦截器中,拦截并获取请求中的AccessToken的value值,根据value获取认证信息,并将认证信息存入上下文中。
    4、注解配置需要拦截的URL,如:@PreAuthorize("hasRole('ADMIN')")表示该接口需要ADMIN角色才能访问。否则将返回403禁止访问提示。

    源代码地址

    诸位看官,小弟技术有限,如上述有误,请指出!

    Spring-Security-OAuth2服务器之搭建认证授权服务器[一]

    Spring-Security-OAuth2服务器搭建之AccessToken的检测[二]

    Spring-Security-OAuth2服务器搭建之资源服务器搭建[三]

    Spring-Security-OAuth2资源服务器及SpringSecurity权限控制[四]

    相关文章

      网友评论

      • 0416119a816f:头像不错,看来老婆能激发个人的潜力。。。

      本文标题:Spring-Security-OAuth2资源服务器及Spri

      本文链接:https://www.haomeiwen.com/subject/jnvgkxtx.html