spring安全spring-security
MR.XSSSpring Security
简介
它是spring家族中的一个安全框架,核心功能是授权与认证
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。提供了完善的认证机制和方法级的授权功能。是一款非常优秀的权限管理框架。它的核心是一组过滤器链,不同的功能经由不同的过滤器
一般的web应用需要进行授权和认证。
- 认证:验证当前访问系统的是不是本系统用户,并且具体到那一个用户
- 授权:经过认证后判定当前用户是否有权限执行某个操作
1.简单上手
1.1 准备工作
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
|
1.2数据库验证
1.2.1引入依赖
1 2 3 4 5 6 7 8 9
| <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
|
配置数据库源:
1 2 3 4 5 6
| spring: datasource: url: jdbc:mysql://localhost:3306/sg_security?characterEncoding=utf-8&serverTimezone=UTC username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver
|
实体类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| import java.io.Serializable; import java.util.Date;
@Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private static final long serialVersionUID = -40356785423868312L;
private Long id;
private String userName;
private String nickName;
private String password;
private String status;
private String email;
private String phonenumber;
private String sex;
private String avatar;
private String userType;
private Long createBy;
private Date createTime;
private Long updateBy;
private Date updateTime;
private Integer delFlag; }
|
1.2.2 实现UserDetailsService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package com.xu.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.xu.dao.UserMapper; import com.xu.pojo.LoginUser; import com.xu.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service;
import java.util.Objects;
@Service public class UserDetailServiceImpl implements UserDetailsService {
@Autowired private UserMapper mapper;
@Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(User::getUserName,s);
User user = mapper.selectOne(queryWrapper);
if (Objects.isNull(user)){ throw new RuntimeException("用户不存在!"); }
return new LoginUser(user); } }
|
1.2.3 实现UserDetails
实现这个接口,注入我们自己的实体类,生成构造器和有参数无参数getter和setter方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package com.xu.pojo;
import lombok.AllArgsConstructor; import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
@Data @AllArgsConstructor public class LoginUser implements UserDetails {
private User user;
@Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; }
@Override public String getPassword() { return user.getPassword(); }
@Override public String getUsername() { return user.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.4 选择加密方式
继承WebSecurityConfigurerAdapter类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.xu.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
|
2.认证
2.1登录校验流程
2.2 spring security执行流程
本质上是一个过滤器链,包含了各种功能的过滤器
UsernamePasswordAuthenticationFilter:对login的post请求做拦截,校验表单中的用户名密码
FilterSecurityInterceptor :是一个方法=级的权限过滤器,基本位于过滤器最底部
ExceptionTranslationFilter: 是一个异常处理器,用来处理在权限认证过程中的异常处理
2.3 认证流程
概念速查
Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。AuthenticationManager接口: 定义了认证Authentication的方法UserDetailsService接口: 加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法UserDetails接口: 提供核心用户信息,通过UserDetailservice根据用户名获取外理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。
2.4 登陆验证
解决思路
登录:
校验:
定义jwt过滤器
获取token
解析token,获取userid
从redis获取用户信息
存入SecurityContextHoder
2.4.1登录接口
自定义登陆接口,需要让SpringSecurity对这个接口放行,让用户访问这个接口的时候不用登录也能访问。
在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器
认证成功的话要生成一个jwt,放入响应中返回。并且为了让用户下回请求时能通过jwt识别出具体的是哪个用户,我们需要把用户信息存入redis,可以把用户id作为key。
登录接口
1 2 3 4 5 6 7 8 9 10 11
| @RestController public class LoginController {
@Autowired private LoginServcie loginServcie;
@PostMapping("/user/login") public ResponseResult login(@RequestBody User user){ return loginServcie.login(user); } }
|
配置类,配置security规则,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); }
@Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/user/login").anonymous() .anyRequest().authenticated(); }
@Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package com.xu.service.impl;
import com.xu.pojo.LoginUser; import com.xu.pojo.User; import com.xu.service.LoginService; import com.xu.util.JwtUtil; import com.xu.util.RedisCache; import com.xu.util.ResponseResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service;
import java.util.HashMap; import java.util.Objects;
@Service public class LoginServiceImpl implements LoginService {
@Autowired private RedisCache redisCache;
@Autowired private AuthenticationManager authenticationManager;
@Override public ResponseResult login(User user) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword()); Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if (Objects.isNull(authenticate)){ throw new RuntimeException("认证失败!"); }
LoginUser principal = (LoginUser) authenticate.getPrincipal(); Long id = principal.getUser().getId(); String jwt = JwtUtil.createJWT(id.toString());
HashMap<String, String> map = new HashMap<>(); map.put("token",jwt);
redisCache.setCacheObject("login:"+id,principal);
return new ResponseResult(200,"登陆成功",map); } }
|
2.4.2 认证过滤器
我们需要自定义一个过滤器,这个过滤器会去获取请求头中的token,对token进行解析取出其中的userid。
使用userid去redis中获取对应的LoginUser对象。
然后封装Authentication对象存入SecurityContextHolder
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| package com.xu.filter;
import com.xu.pojo.LoginUser; import com.xu.util.JwtUtil; import com.xu.util.RedisCache; import io.jsonwebtoken.Claims; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Objects;
@Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired private RedisCache redisCache;
@Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { String token = httpServletRequest.getHeader("token");
if (!StringUtils.hasText(token)) { filterChain.doFilter(httpServletRequest, httpServletResponse); throw new RuntimeException("token是空的!"); }
String userID; try { Claims claims = JwtUtil.parseJWT(token); userID = claims.getSubject(); } catch (Exception e) { throw new RuntimeException("token解析异常!"); }
String key = "login:" + userID; LoginUser loginUser = redisCache.getCacheObject(key);
if(Objects.isNull(loginUser)){ throw new RuntimeException("redis 信息出现错误!"); }
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(httpServletRequest,httpServletResponse); } }
|
将过滤器添加到Spring Security过滤器链中
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired private JwtAuthenticationTokenFilter filter;
@Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(filter,UsernamePasswordAuthenticationFilter.class); } }
|
2.4 退出登录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Override public ResponseResult logout() { UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); LoginUser loginUser = (LoginUser) authentication.getPrincipal();
String ID = loginUser.getUser().getId().toString();
String redisKey = "login:" + ID;
redisCache.deleteObject(redisKey);
return new ResponseResult(200,"注销成功!"); }
|