关于springsecurity的一知半解(一)

注:刚入门springsecurity,本文定是有诸多纰漏,仅作记录用。

最近看到一个springsecurity的项目,研究它的登录,产生了疑问:
1、springsecurity如何存用户登录信息的?
2、springsecurity多用户登录的时候,怎么实现判断哪个请求是哪个用户发的?

spring配置类

@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
    // 配置登录注销路径
    http.formLogin().loginProcessingUrl("/login").successHandler(authenticationSuccessHandler).failureHandler(authenticationFailHandler).and()
            .logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
    // 配置路由权限信息
    http.authorizeRequests()
            .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
    
    
                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
    
    
                    fsi.setSecurityMetadataSource(securityMetadataSource());
                    fsi.setAccessDecisionManager(accessDecisionManager());
                    return fsi;
                }
            })
            .anyRequest().permitAll()
            .and()
            //关闭跨站请求防护
            .csrf().disable().exceptionHandling()
            //未登录处理
            .authenticationEntryPoint(authenticationEntryPoint)
            //权限不足处理
            .accessDeniedHandler(accessDeniedHandler).and()
            //开启嵌入
            .headers().frameOptions().disable()
            .and()
            .sessionManagement()
            .maximumSessions(20)
            .sessionRegistry(sessionRegistry());
}

登录接口实现类

@Override
public UserInfoDTO qqLogin(String openId, String accessToken) {
    
    
    // 创建登录信息
    UserInfoDTO userInfoDTO;
    // 校验该第三方账户信息是否存在
    UserAuth user = getUserAuth(openId, LoginTypeEnum.QQ.getType());
    if (Objects.nonNull(user) && Objects.nonNull(user.getUserInfoId())) {
    
    
        // 存在则返回数据库中的用户信息登录封装
        userInfoDTO = getUserInfoDTO(user);
    } else {
    
    
        // 不存在通过openId和accessToken获取QQ用户信息,并创建用户
        Map<String, String> formData = new HashMap<>(16);
        // 定义请求参数
        formData.put("openid", openId);
        formData.put("access_token", accessToken);
        formData.put("oauth_consumer_key", QQ_APP_ID);
        // 获取QQ返回的用户信息
        Map<String, String> userInfoMap = JSON.parseObject(restTemplate.getForObject(QQ_USER_INFO_URL, String.class, formData), Map.class);
        // 获取ip地址
        String ipAddr = IpUtil.getIpAddr(request);
        String ipSource = IpUtil.getIpSource(ipAddr);
        // 将用户账号和信息存入数据库
        UserInfo userInfo = convertUserInfo(Objects.requireNonNull(userInfoMap).get("nickname"), userInfoMap.get("figureurl_qq_1"));
        userInfoDao.insert(userInfo);
        UserAuth userAuth = convertUserAuth(userInfo.getId(), openId, accessToken, ipAddr, ipSource, LoginTypeEnum.QQ.getType());
        userAuthDao.insert(userAuth);
        // 绑定角色
        saveUserRole(userInfo);
        // 封装登录信息
        userInfoDTO = convertLoginUser(userAuth, userInfo, Lists.newArrayList(RoleEnum.USER.getLabel()), null, null, request);
    }
    // 将登录信息放入springSecurity管理
    UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userInfoDTO, null, userInfoDTO.getAuthorities());
    SecurityContextHolder.getContext().setAuthentication(auth);
    return userInfoDTO;
}

@Override
public UserInfoDTO weiBoLogin(String code) {
    
    
    // 创建登录信息
    UserInfoDTO userInfoDTO;
    // 用code换取accessToken和uid
    MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
    // 定义请求参数
    formData.add("client_id", WEIBO_APP_ID);
    formData.add("client_secret", WEIBO_APP_SECRET);
    formData.add("grant_type", WEIBO_GRANT_TYPE);
    formData.add("redirect_uri", WEIBO_REDIRECT_URI);
    formData.add("code", code);
    // 构建参数体
    HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(formData, null);
    // 获取accessToken和uid
    Map<String, String> result = restTemplate.exchange(WEIBO_ACCESS_TOKEN_URI, HttpMethod.POST, requestEntity, Map.class).getBody();
    String uid = Objects.requireNonNull(result).get("uid");
    String accessToken = result.get("access_token");
    // 校验该第三方账户信息是否存在
    UserAuth user = getUserAuth(uid, LoginTypeEnum.WEIBO.getType());
    if (Objects.nonNull(user) && Objects.nonNull(user.getUserInfoId())) {
    
    
        // 存在则返回数据库中的用户信息封装
        userInfoDTO = getUserInfoDTO(user);
    } else {
    
    
        // 不存在则用accessToken和uid换取微博用户信息,并创建用户
        Map<String, String> data = new HashMap<>(16);
        // 定义请求参数
        data.put("uid", uid);
        data.put("access_token", accessToken);
        // 获取微博用户信息
        Map<String, String> userInfoMap = restTemplate.getForObject(WEIBO_USER_INFO_URI, Map.class, data);
        // 获取ip地址
        String ipAddr = IpUtil.getIpAddr(request);
        String ipSource = IpUtil.getIpSource(ipAddr);
        // 将账号和信息存入数据库
        UserInfo userInfo = convertUserInfo(Objects.requireNonNull(userInfoMap).get("screen_name"), userInfoMap.get("profile_image_url"));
        userInfoDao.insert(userInfo);
        UserAuth userAuth = convertUserAuth(userInfo.getId(), uid, accessToken, ipAddr, ipSource, LoginTypeEnum.WEIBO.getType());
        userAuthDao.insert(userAuth);
        // 绑定角色
        saveUserRole(userInfo);
        // 封装登录信息
        userInfoDTO = convertLoginUser(userAuth, userInfo, Lists.newArrayList(RoleEnum.USER.getLabel()), null, null, request);
    }
    // 将登录信息放入springSecurity管理
    UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userInfoDTO, null, userInfoDTO.getAuthorities());
    SecurityContextHolder.getContext().setAuthentication(auth);
    return userInfoDTO;
}

好,那么大段的代码其实大部分都不重要,关注点在于,他这边有三个登录方法,qq登录,微博登录以及默认的login,其他两个方法都写了这么一个语句

SecurityContextHolder.getContext().setAuthentication(auth);

没错,注释也写了,就是把登录信息存到springsecurity里面去,那么默认的/login方法是不是也有这么一句话呢,根据源码分析,确实有,如下:
在这里插入图片描述
详情参考

https://blog.csdn.net/weixin_42449408/article/details/110264819

那我就在想,其他两个方法难道不走这条过滤链吗,不然存用户的语句不是多余了?通过和大佬的交流,明白了自定义登录逻辑不会走那条过滤链,只有你在配置里开启的 /login才会走。

那么第一个问题就解决了,security的默认登录会自动存登陆成功的用户信息,自定义登录逻辑的接口只能手动存。

第二个问题,怎么判断哪个请求是哪个用户发的,很简单,security底层已经实现了,看下面这个例子。

封装了一个获取当前用户信息的工具类

/**
 * 用户工具类
 */
public class UserUtils {
    
    
    /**
     * 获取当前登录用户
     *
     * @return 用户登录信息
     */
    public static User getLoginUser(){
    
    
        return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}

一个普通的获取信息的接口

    @GetMapping("/groupObject")
    @ApiOperation("查询所有小组目标")
    public Result getGroupObjects(ObjectVo objectVo) {
    
    
        User loginUser = UserUtils.getLoginUser();
        List<Objective> objectives = objectService.getObjects(objectVo);
        PageInfo<Objective> pageInfo = new PageInfo<>(objectives);
        return Result.success().data("page", pageInfo);
    }

首先我在项目上登录了两个不同的用户,然后我分别发请求,调试一下分别能拿到什么用户。结果如下
在这里插入图片描述
在这里插入图片描述
没错,不需要多余的操作,从内存里拿出来的用户就是对应发送请求的用户
大佬说底层逻辑是 threadlocal存储线程变量,而我只能感叹springsecurity真智能。

最后推一下大佬的博客项目,主要是springboot+vue,有兴趣的可以看一眼,本人还在学习

https://github.com/X1192176811/Blog

猜你喜欢

转载自blog.csdn.net/shuttle33/article/details/116530100