赞
踩
环境说明:
SpringBoot 3.2.1 、SpringSecurity 6.1.2
认证:校验用户输入的账号信息,通过后,为其颁发“业务token”和“安全token”
业务token作用:传输用户编号,并根据编号换取“安全token”
<!--springsecurity核心包--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!--用签发业务token包--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency> <!--将java对象在对象和json格式之间转换--> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.39</version> </dependency>
目的:将springsecurity默认的登录逻辑迁移到自定义的逻辑上(访问数据库)
/* 实现 UserDetailsService 接口,自定义校验逻辑 @Component让spring知晓自定义的内容 */ @Component public class SecurityUserDetailServiceImpl implements UserDetailsService { @Autowired private ShopUserService shopUserService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //1.根据用户名查询用户信息 QueryWrapper<ShopUser> qw = new QueryWrapper<>(); qw.eq("user_name",username); ShopUser shopUser = shopUserService.getOne(qw); //2.TODO 查询该用户的权限列表并整合对象 return new SecurityUserDetailsImpl(shopUser); } }
/* 因为Security内部的loadUserByUsername方法需要的返回值必须是UserDetails类型,项目本身提供的用户类型不适用 按照java多态标准,此类符合UserDetails规范,可以用来返回 */ @Data @AllArgsConstructor @NoArgsConstructor public class SecurityUserDetailsImpl implements UserDetails { private ShopUser shopUser; /* 获取校验用户的权限列表 */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } /* 获取系统用户的密码信息,用于security后续的密码对比 */ @Override public String getPassword() { return shopUser.getPassword(); } /* 获取系统用户的用户名信息 */ @Override public String getUsername() { return shopUser.getUserName(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
需要配置以下信息:
1.密码解析器
2.认证管理器(提供了用户信息校验逻辑)
3.对security过滤器链定义
@Configuration //标记此类为一个springboot的配置类 @EnableWebSecurity //开启security基于web开发的安全机制 public class SecurityConfig { @Autowired private SecurityUserDetailServiceImpl securityUserDetailService; @Autowired private SecurityTokenFilter securityTokenFilter; /* 密码加密器, 用户表中的用户密码等敏感信息都需要加密存储 */ @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } //2.配置认证管理器,security框架默认不提供 @Bean public AuthenticationManager authenticationManager(){ DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); //设置securityUserDetailService,告知security框架,按照指定的类进行身份校验 daoAuthenticationProvider.setUserDetailsService(securityUserDetailService); ProviderManager pm = new ProviderManager(daoAuthenticationProvider); return pm; } //3.配置springsecurity的放行路径等信息 @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{ //对所有请求按照以下约定进行拦截和放行 http.authorizeHttpRequests( //requestMatchers 指定匹配路径 //permitAll 让security跳过之前通过requestMatchers匹配到的路径, auth -> auth.requestMatchers("/shopUser/login").permitAll() //anyRequest 指定除requestMatchers匹配路径之外的其他路径 //authenticated 让anyRequest匹配到的所有路径都通过security校验 .anyRequest().authenticated() ); //关闭 防止客户端的 csrf(跨站伪造) 攻击行为 的能力 // 从security过滤器链中撤出 CsrfFilter http.csrf(csrf -> csrf.disable()); //将自定义的token认证过滤器加入到security-filterChian中,并指定其位置 http.addFilterBefore(securityTokenFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }
目的:不再使用springsecurity提供的默认登陆页面,完全自定义登录逻辑
@RestController @RequestMapping("shopUser") public class ShopUserController{ @Resource private ShopUserService shopUserService; @Autowired private AuthenticationManager authenticationManager; @Autowired private JWTUtil jwtUtil; @Autowired private SecurityInfoService securityInfoService; /** * 因为 springsecurity 框架提供的登录页面不适用于当前项目(前后端分离) * @param loginPo * @return */ @RequestMapping(path = "login",method = RequestMethod.POST) public BaseResult login(@RequestBody LoginPo loginPo){ //1.调用security认证方法 //1.1 封装 token 对象 UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginPo.getUserName(),loginPo.getPassword()); //1.2调用管理器的方法 //通过认证的用户将会获得 Authentication ,其中存放用户信息 Authentication authentication = authenticationManager.authenticate(token); //1.3判断结果 // authentication 如果为null,则表示认证失败 if(Objects.nonNull(authentication)){ SecurityUserDetailsImpl securityUserDetails = (SecurityUserDetailsImpl)authentication.getPrincipal(); //将security颁发的后端凭证存入数据库,此处因为暂时不涉及redis,所以json化后存入数据库 ShopUser shopUser = securityUserDetails.getShopUser(); securityInfoService.save(new SecurityInfo(shopUser.getId(), JSON.toJSONString(authentication))); //认证通过,办法业务token HashMap<String, String> serviceToken = jwtUtil.cerateToken(shopUser); return BaseResult.ok(serviceToken); } return BaseResult.error("认证失败"); } }
思想:通过业务token中携带的用户id,换取安全token
@Component public class SecurityTokenFilter extends OncePerRequestFilter { @Autowired private JWTUtil jwtUtil; @Autowired private SecurityInfoService securityInfoService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = request.getHeader("token"); Integer id = null; if(token != null && !token.equals("")){ //校验token合法性 try{ //从 otken 中获取指定的 payload DecodedJWT decodedJWT = jwtUtil.getToken(token); Claim jwtClaim = decodedJWT.getClaim("id"); id = jwtClaim.asInt(); }catch (Exception e){ System.out.println("token 非法!"); return; } //去数据库查询当前用户认证时生成的token QueryWrapper<SecurityInfo> qw = new QueryWrapper(); qw.eq(SecurityInfo.COL_UID,id); SecurityInfo securityInfo = securityInfoService.getOne(qw); String authenticationJson = securityInfo.getAuthenticationJson(); //将存入数据库的安全token转会后端识别的对象 Authentication authentication = JSONObject.parseObject(authenticationJson, Authentication.class); //将它放入security全局上下文 SecurityContextHolder.getContext().setAuthentication(authentication); //放行,请求会进入下一个过滤器 filterChain.doFilter(request,response); }else{ filterChain.doFilter(request,response); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。