首先是引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
默认方案:
首次使用这个空项目的时候他会给你一个默认的账号
账号名为user
密码在控制台输出如下图
这时再进入后端会直接弹到一个/login的地址,输入上述账号密码即可查看路由的具体内容,也可以输出/logout退出登录
springsercurity本质是一个过滤器链
在默认案例中UsernamePasswordAuthenticationFilter中会调用UserDetailService接口的InMemoryUserDetailManager获取用户信息 ,后续我们需要修改成在数据库中查询信息而非内存中
自拟方案:
登录
- 自定义登录接口:调用ProviderManager认证,通过则生成jwt,并把用户数据存入redis,自定义UserDetailService,把从内存查数据改成从数据库查
- 自定义service继承UserDetailService
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //查询用户信息 User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserName, username)); if(Objects.isNull(user)){ throw new RuntimeException("用户名或密码错误"); } String password = user.getPassword(); //todo 查询权限信息 //封装返回 LoginUser loginUser=new LoginUser(); loginUser.setUser(user); return loginUser; }
- 实现LoginUser类,继承的UserDetails
public class LoginUser implements UserDetails
- 这里如果进行测试的话,会报错:PasswordEncoder会拿获得的密码和数据库比对,但是要求数据库内的格式为{id}password,根据id去判断加密方式。所以我们写的时候会用BC替换掉这个方法
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
注:这里使用了bc加密之后数据库的密码也应在添加时替换成加密后的密码.bc提供了matches()和encode()两种方法
- 添加拦截配置,给登录方法放行一下
@Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeRequests() .antMatchers("/user/login").anonymous() .anyRequest().authenticated(); }
- 登录service
public ResponseResult login(User user) { //进行用户认证 UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword()); Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken); if (Objects.isNull(authenticate)) { throw new RuntimeException("用户名或密码错误"); } LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); String id = loginUser.getUser().getId().toString(); String jwt = JwtUtil.createJWT(id); HashMap<String, String> map = new HashMap<>(); map.put("token", jwt); redisCache.setCacheObject("login:"+id,loginUser); return new ResponseResult("登录成功", 200, map); }
- 自定义service继承UserDetailService
- 校验:获取token后解析获取userid,通过redis获取其中用户数据,并将其存入SecurityContextHolder中
- 配置过滤器
@Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired RedisCache redisCache; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //获取token并解析 String token = request.getHeader("token"); if (!StringUtils.hasText(token)) { filterChain.doFilter(request, response); return; } String userId; try { Claims claims = JwtUtil.parseJWT(token); userId = claims.getSubject(); } catch (Exception e) { throw new RuntimeException(e); } //从redis中获取用户信息再存入securityContextHolder String redisKey = "login:" + userId; LoginUser loginUser = redisCache.getCacheObject(redisKey); if(Objects.isNull(loginUser)){ throw new RuntimeException("用户未登录"); } //todo 权限信息还没写 SecurityContextHolder.getContext(). setAuthentication(new UsernamePasswordAuthenticationToken(loginUser,null,null)); filterChain.doFilter(request, response); } }
- 添加过滤器
@Override protected void configure(HttpSecurity http) throws Exception { http .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); }
- 配置过滤器
为什么要存入信息到SecurityContextHolder中:后续步骤都需要从其中取数据,如果是未认证的状态会导致后面不放行
为什么上述两个位置同样用到了UsernamePasswordAuthenticationToken方法但作用不同:Authentication表示当前访问系统的用户,封装了用户相关信息。
在第一个构造函数中,1.5登录service:我们使用了AuthenticationManager.authenticate()这一方法对数据进行逻辑验证,验证失败则返回结果为null
而第二个构造函数中,2.1配置过滤器:这里手动设置安全信息,此时已经通过安全检查了
标签:String,登录,LoginUser,校验,springsecurity,token,user,loginUser,new From: https://www.cnblogs.com/kun1790051360/p/18310046