注:刚入门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,有兴趣的可以看一眼,本人还在学习