Oauth2

本文详细介绍了OAuth2的优化方案,包括兼容性优化、分布式优化、资源服务器自定义异常返回、token失效处理、授权码模式UI定制、网关优化等。其中,针对兼容性问题,通过标识位区分用户类型;分布式环境下,使用Redis持久化session;资源服务器异常返回自定义,处理401和403异常;此外,讨论了如何给予token失效用户匿名权限、自定义授权页面和路径、以及网关处理无权限接口等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

四、优化

优化点如下:

  1. 兼容性问题:
    • 因为既要兼容原始登陆模块,又要兼容新建的管理员模块,所以需要判断是原始用户还是管理员用户。
    • 验证token时需要判断是认证服务颁发的令牌还是老登陆系统颁发的令牌并分别验证。
  2. 分布式问题:ExceptionTransactionFilter中将request请求信息保存到session中,如果是分布式部署,会有访问不到session的问题发生。
  3. 异常返回问题:资源服务器自定义异常返回。

4.1 兼容性优化

生成令牌

逻辑

  1. 音箱作为第三方需要通过授权码模式获取令牌,携带令牌访问资源(用户信息在原始的登陆模块中),校验账号密码时还需要分两种情况如下
    • 密码登陆
    • 验证码登陆
  2. 管理员需要通过密码模式获取令牌(管理员信息在新建的内部用户模块中)

关键点
需要判断账号密码属于原始用户的还是管理员的,当然后端无法判断,所以可以让登录页面发送请求时主动携带标识位,后端通过该标识位来判断。如内部管理系统登陆页面,在页面代码中发送请求时多带上一个标识位参数。

代码

先写一个枚举类记录所有的类型

public enum UserTypeEnum {

    ADMIN(1,"admin","管理员登录"),
    HIFUN_USERNAME(2,"hifun_username","用户账号密码登录"),
    HIFUN_PHONE(3,"hifun_phone","用户手机验证码登录");

    Integer code;
    String codeText;
    String description;

    UserTypeEnum(Integer code, String codeText, String description) {
        this.code = code;
        this.codeText = codeText;
        this.description = description;
    }

    public Integer getCode() {
        return code;
    }

    public String getCodeText() {
        return codeText;
    }

    public String getDescription(){
        return description;
    }

}

注:手机验证码是通过第三方平台接口来做的,用户相关的登陆最终都会调用原有的用户系统的接口进行验证。

通过Feign来调用用户系统的接口

@FeignClient(name = "hifun-service-user")
public interface HifunFeign {

    /**
     * 验证密码
     *
     * @param id
     * @param password
     * @return
     */
    @PostMapping(path = "/password/check", consumes = "application/json")
    String check(@RequestParam("id") Integer id, @RequestBody String password);

    /**
     * 根据手机号获取用户信息
     *
     * @param mobile
     * @return
     */
    @GetMapping(path = "/user/mobile")
    String mobile(@RequestParam("mobile") String mobile);


    /**
     * 极验初始化接口
     *
     * @param clientType
     * @param ip
     * @param smsType
     * @return
     */
    @GetMapping(path = "/geetest")
    String geetest(@RequestParam("clientType") String clientType, @RequestParam("ip") Integer ip, @RequestParam("smsType") String smsType);

    /**
     * 极验二次验证并发送短信验证码
     *
     * @param geetestPO
     * @return
     */
    @PostMapping(path = "/geetest")
    String geetest(@RequestBody GeetestPO geetestPO);

    /**
     * 校验手机和验证码是否正确
     *
     * @return
     */
    @PostMapping(path = "/login/code")
    String code(@RequestBody CodePO codePO);

    @PostMapping(path = "/password/findPassword")
    void findPassword(@RequestBody PasswordPO passwordPO);

}

重写MyUserDetailsServer类中的loadUserByUsername方法

@Service
@Slf4j
public class MyUserDetailsServer implements UserDetailsService {

    @Resource
    private HifunFeign hifunFeign;

    @Resource
    private UserCenterFeign userCenterFeign;

    @Resource
    private HttpServletRequest httpServletRequest;

    /**
     * 将账号密码和权限信息封装到UserDetails对象中返回
     *
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//        String userType = httpServletRequest.getHeader("User-Type");
        String userType = httpServletRequest.getParameter("user_type");
        System.out.println("*****loadUserByUsername  userType:" + userType);

        //查询数据库获取用户的信息
        if (UserTypeEnum.ADMIN.getCodeText().equals(userType)) {
            // 1.请求头是admin,查询到管理人员数据库
            TbUserPO tbUser = userCenterFeign.getTbUser(username);
            Assert.isTrue(!Objects.isNull(tbUser), "管理员不存在");
            //将用户信息添加到token中
//            UserInfoDTO userInfo = BeanUtil.copyProperties(tbUser, UserInfoDTO.class);
//            JSONObject userObj = new JSONObject(userInfo);
//            String userStr = userObj.toString();
            tbUser.setUsername(tbUser.getId().toString());
            //获取用户的角色和权限
            List<String> roleCodes = userCenterFeign.getRoleCodes(username);
            List<String> authorities = userCenterFeign.getAuthorities(roleCodes);

            //将用户角色添加到用户权限中
            authorities.addAll(roleCodes);

            //设置UserDetails中的authorities属性,需要将String类型转换为GrantedAuthority
            MyUserDetails myUserDetails = BeanUtil.copyProperties(tbUser, MyUserDetails.class);
            myUserDetails.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", authorities)));

            log.info("UserDetail:" + myUserDetails);
            return myUserDetails;
        } else if (UserTypeEnum.HIFUN_PHONE.getCodeText().equals(userType)) {
            // 2.请求头是hifun_phone,查询火粉的用户中心
//            String userInfo;
//            try {
//                userInfo = hifunFeign.mobile(username);
//            } catch (Exception e) {
//                throw new IllegalArgumentException("该用户不存在");
//            }
//            JSONObject jsonObject = new JSONObject(userInfo);
//            Integer id = jsonObject.getInt("id");

            return new MyUserDetails().setUsername(username).setPassword("hifun")
                    .setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", "hifun")))
                    .setAccountNonExpired(true)
                    .setAccountNonLocked(true)
                    .setCredentialsNonExpired(true)
                    .setEnabled(true);

            //这个User对象是校验client的账号密码时使用的,expire、lock等信息自动填充为true
//            return new User(id.toString(), "hifun", AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", "hifun")));
        } else if (UserTypeEnum.HIFUN_USERNAME.getCodeText().equals(userType)) {
            String userInfo;
            try {
                userInfo = hifunFeign.mobile(username);
            } catch (Exception e) {
                throw new IllegalArgumentException("该用户不存在");
            }
            JSONObject jsonObject = new JSONObject(userInfo);
            Integer id = jsonObject.getInt("id");

            return new User(id.toString(), "hifun", AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", "hifun")));
        }
//        else {
//            return new User("test", "hifun", AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", "hifun")));
//        }
        throw new IllegalArgumentException("未传递用户类型或用户类型不存在");
    }
}

重写DaoAuthenticationProvider类中的additionalAuthenticationChecks方法

@Component
public class MyDaoAuthenticationProvider extends DaoAuthenticationProvider {

    @Resource
    private HifunFeign hifunFeign;

    @Resource
    private BCryptPasswordEncoder bCryptPasswordEncoder;


    public MyDaoAuthenticationProvider(UserDetailsService userDetailsService) {
        super();
        // 这个地方一定要对userDetailsService赋值,不然userDetailsService是null
        setUserDetailsService(userDetailsService);
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert requestAttributes != null;
        HttpServletRequest httpServletRequest = requestAttributes.getRequest();
        HttpServletResponse httpServletResponse = requestAttributes.getResponse();

        String presentedPassword = authentication.getCredentials().toString();

//        Cookie[] cookies = httpServletRequest.getCookies();
//        for (Cookie cookie : cookies) {
//            System.out.println("*****cookie:" + cookie.getName());
//            cookie.setMaxAge(0);
//            cookie.setPath("/");
//            assert httpServletResponse != null;
//            httpServletResponse.addCookie(cookie);
//        }

        String userType = httpServletRequest.getParameter("user_type");
        System.out.println("*****additionalAuthenticationChecks userType:" + userType);

        if (authentication.getCredentials() == null) {
            logger.debug("Authentication failed: no credentials provided");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }


        // TODO 根据请求头的用户类型进行查询
        if (UserTypeEnum.ADMIN.getCodeText().equals(userType)) {
            // 1.请求头是admin,查询到管理人员数据库
            System.out.println("user_type是admin,查询到管理人员数据库");
            if (!bCryptPasswordEncoder.matches(presentedPassword, userDetails.getPassword())) {
                logger.debug("Authentication failed: password does not match stored value");

                throw new BadCredentialsException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.badCredentials",
                        "Bad credentials"));
            }
        } else if (UserTypeEnum.HIFUN_PHONE.getCodeText().equals(userType)) {
            // 2.请求头是hifun_phone,校验手机验证码是否正确
            System.out.println("user_type是hifun_phone,校验手机验证码是否正确");
            String phone = userDetails.getUsername();

            CodePO codePO = new CodePO(httpServletRequest.getParameter("app")
                    , (long) IpUtils.getLongIp(), phone, ""
                    , Long.valueOf(httpServletRequest.getParameter("terminal"))
                    , httpServletRequest.getParameter("uuid"), presentedPassword);
            System.out.println("*****additionalAuthenticationChecks  codePO:" + codePO);
            try {
                String code = hifunFeign.code(codePO);
                System.out.println("*****additionalAuthenticationChecks code:" + code);
            } catch (Exception e) {
                logger.debug("Authentication failed: password does not match stored value");
                try {
                    httpServletRequest.setAttribute("message", e.getMessage());
                    httpServletRequest.getRequestDispatcher("/uaa/error").forward(httpServletRequest, httpServletResponse);
                } catch (Exception ex) {
                    e.printStackTrace();
                    throw new IllegalArgumentException(e.getMessage());
                }
                throw new IllegalArgumentException("停止程序直接返回结果");
//                throw new BadCredentialsException(messages.getMessage(
//                        "AbstractUserDetailsAuthenticationProvider.badCredentials",
//                        "Bad credentials"));
            }
        } else if (UserTypeEnum.HIFUN_USERNAME.getCodeText().equals(userType)) {
            // 3.请求头是hifun_username,校验手机密码是否正确
            System.out.println("user_type是hifun_username,校验手机密码是否正确");
            HashMap<Object, Object> map = new HashMap<>();
            map.put("password", presentedPassword);
            System.out.println("*****additionalAuthenticationChecks  id:" + userDetails.getUsername() + " --- password:" + presentedPassword);
            String check = hifunFeign.check(Integer.parseInt(userDetails.getUsername()), new JSONObject(map).toString());
            JSONObject jsonObject = new JSONObject(check);
            JSONObject data = jsonObject.getJSONObject("data");
            Integer success = data.getInt("success");
            if (success == 0) {
                logger.debug("Authentication failed: password does not match stored value");
                try {
                    httpServletRequest.setAttribute("message", "账号密码错误!");
                    httpServletRequest.getRequestDispatcher("/uaa/error").forward(httpServletRequest, httpServletResponse);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new IllegalArgumentException(e.getMessage());
                }
                throw new IllegalArgumentException("停止程序直接返回结果");
//                throw new BadCredentialsException(messages.getMessage(
//                        "AbstractUserDetailsAuthenticationProvider.badCredentials",
//                        "Bad credentials"));
            }
        }

        String type = httpServletRequest.getHeader("type");
        if ("option".equals(type)) {
            System.out.println("*****type:" + type);
            try {
                httpServletRequest.setAttribute("message", "验证成功!");
                httpServletRequest.getRequestDispatcher("/uaa/success").forward(httpServletRequest, httpServletResponse);
                throw new IllegalArgumentException("停止程序直接返回结果");
            } catch (Exception e) {
                e.printStackTrace();
                throw new IllegalArgumentException(e.getMessage());
            }
//        } else {
//            Cookie[] cookies = httpServletRequest.getCookies();
//            for (Cookie cookie : cookies) {
//                System.out.println("*****cookie:" + cookie.getName());
//                cookie.setMaxAge(0);
//                cookie.setPath("/");
//                assert httpServletResponse != null;
//                httpServletResponse.addCookie(cookie);
//            }
//            Cookie session = new Cookie("SESSION", null);
//            s
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pxr007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值