美文网首页spring security 以及 oauth2
spring security oauth2资源服务器的获取资源

spring security oauth2资源服务器的获取资源

作者: virtual灬zzZ | 来源:发表于2021-10-12 00:45 被阅读0次

    资源服务器配置(这里以数据库配置token方式)

    @Configuration
    @EnableResourceServer
    @EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true, securedEnabled = true)
    public class Oauth2JdbcResourceConfig extends ResourceServerConfigurerAdapter {
    
        private static final String RESOURCE_ID = "hahaRsId";
    
        @Autowired
        private DataSource dataSource;
    
        @Autowired
        private CustomAccessDeniedHandler customAccessDeniedHandler;
    
        @Autowired
        private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
    
            http.csrf().disable();
            http.authorizeRequests()
                    .antMatchers("/myoauth/**").authenticated();
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    
        }
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.resourceId(RESOURCE_ID)
                    .tokenStore(jdbcTokenStore())
                    .stateless(true)
                    .authenticationEntryPoint(customAuthenticationEntryPoint)
                    .accessDeniedHandler(customAccessDeniedHandler);
        }
    
        @Bean
        public TokenStore jdbcTokenStore(){
            return new JdbcTokenStore(dataSource);
        }
    
    

    获取资源过程:

    先经过 OAuth2AuthenticationProcessingFilter 这个filter,到数据库验证这个access_token,token在数据库的id是经过md5加密的。

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
                ServletException {
    
            final boolean debug = logger.isDebugEnabled();
            final HttpServletRequest request = (HttpServletRequest) req;
            final HttpServletResponse response = (HttpServletResponse) res;
    
            try {
    
                Authentication authentication = tokenExtractor.extract(request);
                
                if (authentication == null) {
                    if (stateless && isAuthenticated()) {
                        if (debug) {
                            logger.debug("Clearing security context.");
                        }
                        SecurityContextHolder.clearContext();
                    }
                    if (debug) {
                        logger.debug("No token in request, will continue chain.");
                    }
                }
                else {
                    request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
                    if (authentication instanceof AbstractAuthenticationToken) {
                        AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
                        needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
                    }
                    Authentication authResult = authenticationManager.authenticate(authentication);
    
                    if (debug) {
                        logger.debug("Authentication success: " + authResult);
                    }
    
                    eventPublisher.publishAuthenticationSuccess(authResult);
                    SecurityContextHolder.getContext().setAuthentication(authResult);
    
                }
            }
            catch (OAuth2Exception failed) {
                SecurityContextHolder.clearContext();
    
                if (debug) {
                    logger.debug("Authentication request failed: " + failed);
                }
                eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
                        new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
    
                authenticationEntryPoint.commence(request, response,
                        new InsufficientAuthenticationException(failed.getMessage(), failed));
    
                return;
            }
    
            chain.doFilter(request, response);
        }
    

    authentication 就是起始认证的用户,不是clientSecret和clientId,而是userName和password。最后得到的权限authority也是用户的权限,不是client_detail中配置的权限,比如userName的权限是a,b,client_detail的权限是c,d,最终的权限就是a,b,

    用户的权限:

    client_detail的权限

    验证权限过程

          //正常请求
        @GetMapping("/api")
        @PreAuthorize("hasAuthority('测试呢5')") 
        public String api() {
            return "my api";
        }
    
        //正常请求
        @GetMapping("/test6")
        @PreAuthorize("hasAuthority('测试呢6')")
        public String test6() {
            return "test6";
        }
    
          //权限不足,accessDeny
        @GetMapping("/author1")
        @PreAuthorize("hasAuthority('author1')")
        public String author1() {
            return "author1";
        }
    

    这里还必须注意的是

    认证端和资源端分离,authentication即username这用户的类必须是一模一样的,因为数据库存的就是blob类型,里面含有class的类型,还有accessToken也必须是一模一样,在验证的过程中会解析出来,如果不一致,就是报错,而且还会删除正常的access_token,能debug出来。

    比如认证端的user(实现了userDetail)

    @Service("authUserDetailService")
    public class AuthUserDetailService implements UserDetailsService {
    
        @Resource(name = "userDao")
        private UserDao userDao;
    
        @Resource(name = "roleDao")
        private RoleDao roleDao;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User user = userDao.selectOne(new LambdaQueryWrapper<User>().eq(User::getAccount, username));
            if (user == null) {
                throw new BadCredentialsException("用户[" + username + "] 没找到");
            }
    
            Integer status = user.getStatus();
            if (status == 0) {
                throw new DisabledException("status=0 无效用户");
            }
    
            if (status == 2) {
                throw new CredentialsExpiredException("status=2 密码过期");
            }
    
            if (status == 3) {
                throw new LockedException("status=3 账户已经锁定");
            }
    
            if (status == 4) {
                throw new CommonAuthenticationException("status=4 CommonAuthentication,哈哈哈");
            }
    
            List<Role> roles = roleDao.selectRoleByUserId(user.getId());
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            if (!roles.isEmpty()) {
                List<String> roleNames = roles.stream().map(Role::getRoleName).collect(Collectors.toList());
                for (String roleName : roleNames) {
                    authorities.add(new SimpleGrantedAuthority(roleName));
                }
            }
    
            return new SysUser(username, user.getPassword(), authorities);
        }
    

    认证端自定义的accessToken----MyAccessToken(记得自定义序列化器),因为这里要配置成统一的json返回格式,带code和msg

    @JsonSerialize(using = MyOauth2AccessTokenJsonSerializer.class)
    public class MyOauth2AccessToken extends DefaultOAuth2AccessToken {
        public MyOauth2AccessToken(String value) {
            super(value);
        }
    
        public MyOauth2AccessToken(OAuth2AccessToken accessToken) {
            super(accessToken);
        }
    }
    
    public class MyOauth2AccessTokenJsonSerializer extends StdSerializer<MyOauth2AccessToken> {
    
        protected MyOauth2AccessTokenJsonSerializer() {
            super(MyOauth2AccessToken.class);
        }
    
        @Override
        public void serialize(MyOauth2AccessToken token, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            CommonCodeEnum commonSuccess = CommonCodeEnum.COMMON_SUCCESS;
            Result<Map> result = new Result<>(commonSuccess.getMsg(),commonSuccess.getCode());
    
            //Map<String, Object> map = BeanUtil.beanToMap(token);
    
            Map<String, Object> map = new HashMap<>(16);
            map.put(OAuth2AccessToken.ACCESS_TOKEN, token.getValue());
            map.put(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType());
            OAuth2RefreshToken refreshToken = token.getRefreshToken();
            if (refreshToken != null) {
                map.put(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.getValue());
            }
            Date expiration = token.getExpiration();
            if (expiration != null) {
                long now = System.currentTimeMillis();
                map.put(OAuth2AccessToken.EXPIRES_IN, (expiration.getTime() - now) / 1000);
            }
            Set<String> scope = token.getScope();
            if (scope != null && !scope.isEmpty()) {
                StringBuilder scopes = new StringBuilder();
                for (String s : scope) {
                    Assert.hasLength(s, "Scopes cannot be null or empty. Got " + scope + "");
                    scopes.append(s);
                    scopes.append(" ");
                }
                map.put(OAuth2AccessToken.SCOPE, scopes.substring(0, scopes.length() - 1));
            }
            Map<String, Object> additionalInformation = token.getAdditionalInformation();
            for (String key : additionalInformation.keySet()) {
                map.put(key, additionalInformation.get(key));
            }
    
    
            result.setData(map);
    
            jsonGenerator.writeObject(result);
        }
    }
    
    
    {
        "msg": "Success",
        "code": 1000,
        "data": {
            "access_token": "0df06dc5-a728-4c71-83bd-43e6d77178b6",
            "refresh_token": "a4a99fea-bdd1-4356-a581-ff679fba92f3",
            "scope": "user_info",
            "customInfo": "extra thing额外的东西",
            "token_type": "bearer",
            "expires_in": 10799
        }
    }
    

    debug中显示的类型,类型必须一致 ,所以最好都继承相同的子模块,把这些公共的类放在子模块中。

    相关文章

      网友评论

        本文标题:spring security oauth2资源服务器的获取资源

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