spring security 以及 oauth2

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

2021-10-12  本文已影响0人  virtual灬zzZ

资源服务器配置(这里以数据库配置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中显示的类型,类型必须一致 ,所以最好都继承相同的子模块,把这些公共的类放在子模块中。

上一篇下一篇

猜你喜欢

热点阅读