Oauth2登录后访问接口权限校验多出 authority=字符串校验失败( 已解决)

一、发生的问题

  • oauth登录模块登录成功后,带着token去访问带权限的接口,发生 Spring Security: 不允许访问 的问题
  • 查看权限断点发现是携带的权限被多加了一段字符串
    在这里插入图片描述
前提:
  • 自定义实现UserDetailsService 加载用户信息和权限 到UserDetails对象
    在这里插入图片描述
package com.lxy.blog.config;

import com.lxy.blog.service.api.UserFeignClient;
import domain.SecurityUserDetails;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import util.BeanUtil;

/**
 *@author liuxingying
 *    这个类就是用户来请求获取token时,根据该用户的username加载用户信息返回即可,
 *   而校验用户账号密码的操作之后会被AuthenticationManager调用一个认证器去校验用户账号密码完成
 *@since 2020/12/31
 */
@Slf4j
@Service("ouath2UserDetailService")
public class Ouath2UserDetailService implements UserDetailsService {

    @Autowired
    private UserFeignClient userFeignClient;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Assert.notNull(username,"aut username can not be null");
        // (省略.......)创建UserTokenVo,它就是 要继承UserDetails接口,添加稍后要存放到token里的字段到这个类(稍后自定义实现token增强器),
       /* SecurityUserDetails user = new SecurityUserDetails();
        user.setUserId("001");
        user.setNickName("lxy");
        user.setPassword("$2a$10$KjvPVs6ast3f34CfKQR8m.QfUVqfPE5oBrzke.asucA/v7EN4y49O");*/

        return checkEmpty(userFeignClient.loadUserByUsername(username));
    }

    /**
     *
     * null 处理
     *
     * @author liuxingying
     * @since 2020/12/31
     **/
    private SecurityUserDetails checkEmpty(SecurityUserDetails securityUserDetails) {
        if (securityUserDetails == null){
            return null;
        }
        return securityUserDetails;
    }
}

    @Override
    public SecurityUserDetails loadUserByUsername(String username) {
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getName, username));

        if (ObjectUtils.isEmpty(user)){
            throw BusinessException.busExp(MemberExceptionCode.BIZ_ACCOUNT_UNREGISTERED);
        }
        SecurityUserDetails userDetails = new SecurityUserDetails();
        userDetails.setUsername(user.getName());
        userDetails.setNickName(user.getNickname());
        userDetails.setPassword(user.getPassword());
        // TODO 用户权限 暂设假数据

        List<SecurityAuthority> list = new ArrayList<>();

        SecurityAuthority authRole = new SecurityAuthority();
        authRole.setAuthority("read");
        list.add(authRole);
        userDetails.setAuthorities(list);

        //userDetails.setAuthorities(Collections.singletonList(authRole));
        return userDetails;
    }
  • 访问权限接口:

    @ApiOperation(value = "根据id查询", notes = "")
    @GetMapping("/info")
    @PreAuthorize("hasAuthority('read')")
    public Result<User> getByInfo() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        User lxy = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getName, "lxy"));
        return new Result<>(lxy);
    }

二、问题来了:

Spring Security: 不允许访问,为什么会出现这样的问题呢?我明明携带的是"read"权限,但是读取却是"authority=read"

  • 首先我们来简单看看oauth是怎么运作的,请求登录接口时,将有两行代码在权限管理器中进行获取用户基本信息和权限,并设置到SecurityContextHolder全文中
  • Authentication authentication = authenticationManager.authenticate(token);
    SecurityContextHolder.getContext().setAuthentication(authentication);
public OAuth2AccessToken checkToken(HttpServletRequest request, AbstractAuthenticationToken token, String grantType) {
        try {
            String clientId = request.getHeader("client_id");
            String clientSecret = request.getHeader("client_secret");
            if (StringUtils.isEmpty(clientId)) {
                throw new UnapprovedClientAuthenticationException("请求头中无client_id信息");
            }
            if (StringUtils.isEmpty(clientSecret)) {
                throw new UnapprovedClientAuthenticationException("请求头中无client_secret信息");
            }
            ClientDetails clientDetails = getClient(clientId, clientSecret, null);
            TokenRequest tokenRequest = new TokenRequest(new HashMap<>(), clientId, clientDetails.getScope(), grantType);
            OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
            Authentication authentication = authenticationManager.authenticate(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
            OAuth2AccessToken oAuth2AccessToken =  authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
            oAuth2Authentication.setAuthenticated(true);
            return oAuth2AccessToken;
        } catch (AuthenticationException e){
            log.error("登录失败!", e.getMessage());
            int code = 400401;
            String msg = ExceptionCode.SYS_SERVICE_BUSY.getMessage();
            if (e instanceof LockedException) {
                msg = "账户被锁定,请联系管理员!";
                code = 400402;
            } else if (e instanceof CredentialsExpiredException) {
                msg = "密码过期,请联系管理员!";
                code = 400403;
            } else if (e instanceof AccountExpiredException) {
                msg = "账户过期,请联系管理员!";
                code = 400404;
            } else if (e instanceof DisabledException) {
                msg = "账户被禁用,请联系管理员!";
                code = 400405;
            } else if (e instanceof BadCredentialsException) {
                msg = "账号或者密码输入错误,请重新输入!";
                code = 400406;
            } else if (e instanceof InternalAuthenticationServiceException) {
                msg = "账号不存在,请重新输入!";
                code = 400407;
            } else if (e instanceof LoginAuthenticationServiceException){
                LoginAuthenticationServiceException ex = (LoginAuthenticationServiceException)e;
                throw new BusinessException(ex.getCode(), ex.getMessage());
            }
            throw new BusinessException(code, msg);
        }
  • 在这过程中会将自定义的OAuth2AccessToken封装好并且设置到tokenStore中,并且返回给前端做展示
/**
 *@author liuxingying
 *@description
 *@since 2020/12/31
 */
public class CustomTokenEnhancer implements TokenEnhancer {

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        // 这个UserTokenVo就是之前UserDetial返回的对象
        //从那获取要增强携带的字段
        SecurityUserDetails user = (SecurityUserDetails) authentication.getPrincipal();

        final Map<String, Object> additionalInfo = new HashMap<>();

        //添加token携带的字段
//        additionalInfo.put("userId", user.getUserId());
        additionalInfo.put("nickname", user.getNickName());
        additionalInfo.put("authorities", user.getAuthorities());

        DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
        token.setAdditionalInformation(additionalInfo);

        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);

        return accessToken;
    }
}

问题就出在这儿:我想将用户的权限列表也返回给前端,就出现了后续的bug

  • 为什么呢?因为 additionalInfo.put(“authorities”, user.getAuthorities());前面讲了,自定义的OAuth2AccessToken封装好会被设置到tokenStore中,源码如下
    在这里插入图片描述
  • 登录成功会携带OAuth2AccessToken对象返回前端
    在这里插入图片描述
  • 此时拿到token后我们就用token去请求需要权限校验的接口,在oauth底层此时会先将token带入tokenStore中进行处理取得权限对象
    在这里插入图片描述
  • 我是用的jwt,所以会进行decode解析token
    在这里插入图片描述
  • 将token中解析后的map传入DefaultAccessTokenConverter默认访问令牌转换器中
    在这里插入图片描述
    执行方法:Authentication user = userTokenConverter.extractAuthentication(map);
    在这里插入图片描述
    执行方法:Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
    在这里插入图片描述
  • 注意:此时就出现问题了,我之前是将权限的列表作为map的值封装到了Oauth2token对象中,此时就会去获取map值,如果没有就取默认的登录时自定义的SecurityUserDetails 对象中的 authorities权限列表值 private List authorities;
    但是此时我map的key同名了,也写作了authorities,所以就从map中取,然后用工具方法去把列表转换了,但是我map的authorities对应的value是一个对象,直接给我转换了对象属性=值,导致我再权限接口进行权限对比的时候对应不上

三、总结

所以直接去掉自定义map的权限信息的或者将key名字换成与authorities不一样的就可以了,建议还是去掉,我是手残加上然后就看了半天源码才看到问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值