Spring-Security框架用于实现用户登录认证和权限认证,只要用户不登陆或者没有相应的权限就不能访问和请求数据,通过JWT来判断用户是否符合。
JWT类似于Session,是一种用于认证,登录的令牌。不同于session存储于服务器内存中,而是保存在用户手中。
一、在登陆成功时创建JWT
Authentication authentication = new UsernamePasswordAuthenticationToken(
adminLoginInfoDTO.getUsername(),
adminLoginInfoDTO.getPassword());
//开始认证
Authentication authenticateResult =
authenticationManager.authenticate(authentication);//认证结果
1.1 登录验证,authenticate()(自动调用UserDetailsServiceImpl中重写的loadUserBy Username 方法)验证所传的的用户名和密码。authentication用来创建验证所需要的的信息。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
AdminMapper adminMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException{
log.debug("Spring Security调用了loadUserByUsername()方法,参数:{}", s);
AdminLoginInfoVo loginInfoByUserName = adminMapper.getLoginInfoByUserName(s);
log.debug("数据库调用了参数:{}", loginInfoByUserName.getUsername());
if (loginInfoByUserName == null) {
log.debug("此用户名【{}】不存在,即将抛出异常");
String message = "登录失败,用户名不存在!";
throw new BadCredentialsException(message);
}
List<GrantedAuthority> authorities = new ArrayList<>();
//获取权限放入
for (String permission : loginInfoByUserName.getPermissions()) {
GrantedAuthority authority = new SimpleGrantedAuthority(permission);
authorities.add(authority);
}
AdminDetails adminDetails = new AdminDetails(
loginInfoByUserName.getUsername(),
loginInfoByUserName.getPassword(),
loginInfoByUserName.getEnable() == 1,
authorities);
adminDetails.setId(loginInfoByUserName.getId());
return adminDetails;
}
}
验证成功后返回adminDetails 对象,对象包含名字、密码、是否启用、权限。
1.2 用户密码无误后,创建JWT
//认证返回结果
//单独获取结果转换为user类型
Object principal = authenticateResult.getPrincipal();
AdminDetails user = (AdminDetails) principal;
//从登录认证中获取用户名字密码
Map<String, Object> claims = new HashMap<>();
claims.put("id", user.getId());//todo id tianjia
claims.put("username", user.getUsername());
//过期时间
Date date = new Date(System.currentTimeMillis() + 10 * 24 * 10 * 60 * 1000);
String secretKey = "kns439a}fdLK34jsmfd{MF5-8DJSsLKhJNFDSjn";
//添加到JWYT对象中
String jwt = Jwts.builder()
//header 加密方式和类型
.setHeaderParam("alg", "HS256")
.setHeaderParam("typ", "JWT")
//payload 携带参数和过期时间
.setClaims(claims)
.setExpiration(date)
//Signature 加密方式和盐值
.signWith(SignatureAlgorithm.HS256, secretKey)
//整合
.compact();
return jwt;
二、将认证信息存入SecurityContext中
2.1获取用户携带的JWT
如果用户没有携带JWT放行,过滤器会让用户去访问Security的白名单,去获取JWT。
public class JwtAuthorizationFilter extends OncePerRequestFilter {
@Value("${csmall.jwt.secret-key}")
String secretKey;
private static final long JWT_MIN_LENGTH = 100;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
response, FilterChain filterChain) throws ServletException, IOException {
//尝试获取用户的JWT
String jwt = request.getHeader("Authorization");
log.debug("接收到jwt数据:{}", jwt);
//判断是否获取到jwt StringUtils.hasText(jwt)非空,非null
if (!StringUtils.hasText(jwt) || jwt.length() < JWT_MIN_LENGTH) {
log.trace("未获取到jwt直接放行");
filterChain.doFilter(request, response);
return;
}
对获取的JWT解析判断是否通过Jwts.parser().setSigningKey(secretKey) .parseClaimsJws (jwt) . getBody();解析数据是否正确。
//判断验证是否通过
Claims claims=null;
try {
claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
} catch (ExpiredJwtException e) {
String message="JWT过期,请重新登录";
JSONResult jsonResult =JSONResult.fail(ServiceCode.ERR_JWT_SIGNATURE,message);
//转化Json数据
String jsonResultString = JSON.toJSONString(jsonResult);
PrintWriter printWriter = response.getWriter();
printWriter.println(jsonResultString);
printWriter.close();
return;
} catch (UnsupportedJwtException e) {
JSONResult jsonResult =JSONResult.fail(ServiceCode.ERR_BAD_REQUEST,e.getMessage());
//转化Json数据
String jsonResultString = JSON.toJSONString(jsonResult);
PrintWriter printWriter = response.getWriter();
printWriter.println(jsonResultString);
printWriter.close();
return;
} catch (MalformedJwtException e) {
String message="非法访问";
JSONResult jsonResult =JSONResult.fail(ServiceCode.ERR_JWT_MALFORMED,message);
//转化Json数据
String jsonResultString = JSON.toJSONString(jsonResult);
PrintWriter printWriter = response.getWriter();
printWriter.println(jsonResultString);
printWriter.close();
return;
} catch (SignatureException e) {
JSONResult jsonResult =JSONResult.fail(ServiceCode.ERR_BAD_REQUEST,e.getMessage());
//转化Json数据
String jsonResultString = JSON.toJSONString(jsonResult);
PrintWriter printWriter = response.getWriter();
printWriter.println(jsonResultString);
printWriter.close();
return;
} catch (IllegalArgumentException e) {
String message="非法访问";
JSONResult jsonResult =JSONResult.fail(ServiceCode.ERR_JWT_SIGNATURE,message);
//转化Json数据
String jsonResultString = JSON.toJSONString(jsonResult);
PrintWriter printWriter = response.getWriter();
printWriter.println(jsonResultString);
printWriter.close();
return;
}catch (Throwable e){
e.printStackTrace();
String message="服务器忙";
JSONResult jsonResult =JSONResult.fail(ServiceCode.ERR,message);
//转化Json数据
String jsonResultString = JSON.toJSONString(jsonResult);
PrintWriter printWriter = response.getWriter();
printWriter.println(jsonResultString);
printWriter.close();
return;
}
2.2将认证信息存入到上下文中
判断通过后将数据加入到SecurityCountext中,Security框架会自动判断SecurityCountext中是否存在认证信息,来决定是否放行。
//解析获取用户数据,setContentType设置文档类型
log.trace("开始解析用户数据");
response.setContentType("application/json;charset=utf-8");
Long id = claims.get("id", Long.class);
String username = claims.get("username", String.class);
String authoritiesString = claims.get("authorities", String.class);
log.trace("id: {}", id);
log.trace("username: {}", username);
log.trace("authoritiesString: {}", authoritiesString);
//通过解析出来的数据创建认证
//加入权限,将JSON类型转换为SimpleGrantedAuthority类SimpleGrantedAuthority是GrantedAuthority的实现类
List<SimpleGrantedAuthority> authorities=JSON.parseArray(authoritiesString,
SimpleGrantedAuthority.class);
//创建放入SecurityContext中JWT
LoginPrincipal loginPrincipal=new LoginPrincipal();
loginPrincipal.setId(id);
loginPrincipal.setUsername(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(
loginPrincipal, null, authorities
);
//将认证添加到SecurityContext
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);
//放行
filterChain.doFilter(request, response);
}
}
将过滤器加入到bean中
//添加过滤器
@Autowired
JwtAuthorizationFilter authorizationFilter;
//添加自己的过滤器到密码验证之前
http.addFilterBefore(authorizationFilter,
UsernamePasswordAuthenticationFilter.class);
三、前端登录成功后对JWT的处理
3.1登录成功后会将jwt存放到 localStorage中
console.log('登录成功,服务器响应的JWT=' + jwt);
localStorage.setItem('jwt', jwt);
3.2其他的页面也需要将jwt存储在请求头中
在对后端发送请求时,会携带jwt,过滤器再次验证。
this.axios
.create({'headers': {'Authorization': localStorage.getItem('jwt')}})
.post(url).then((response) => {})
四、Security框架加密处理
Security配置类,对密码加密和解密
@Bean
public PasswordEncoder passwordEncoder() {
//密码判断,他返回的这个就是密码加密的格式,所以他自动调用authenticate方法就会知道你的加密方式
log.trace("SecurityConfiguration 正在加载");
return new BCryptPasswordEncoder();
//return NoOpPasswordEncoder.getInstance(); // 无操作的密码编码器,即:不会执行加密处理
}
@Bean
public AuthenticationManager authenticationManager() throws Exception {
//判断注入,框架自带的,只需要注入就可以掉用authenticate自动判断密码用户是否正确
return super.authenticationManager();
}
在用户注册时调用此方法,对密码加密
String encode = passwordEncoder.encode(rowPassword);
a.setPassword(encode);
在用户登陆时调用authenticationManager.authenticate对密码解析判断
Authentication authentication = new UsernamePasswordAuthenticationToken(
adminLoginInfoDTO.getUsername(),
adminLoginInfoDTO.getPassword());
//开始认证
Authentication authenticateResult = authenticationManager.authenticate(authentication);
authenticate是自动调用一下类的方法做判断。
@Slf4j
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
AdminMapper adminMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
log.debug("Spring Security调用了loadUserByUsername()方法,参数:{}", s);
AdminLoginInfoVo loginInfoByUserName = adminMapper.getLoginInfoByUserName(s);
log.debug("数据库调用了参数:{}", loginInfoByUserName.getUsername());
if (loginInfoByUserName == null) {
log.debug("此用户名【{}】不存在,即将抛出异常");
String message = "登录失败,用户名不存在!";
throw new BadCredentialsException(message);
}
List<GrantedAuthority> authorities = new ArrayList<>();
//获取权限放入
for (String permission : loginInfoByUserName.getPermissions()) {
GrantedAuthority authority = new SimpleGrantedAuthority(permission);
authorities.add(authority);
}
AdminDetails adminDetails = new AdminDetails(
loginInfoByUserName.getUsername(),
loginInfoByUserName.getPassword(),
loginInfoByUserName.getEnable() == 1,
authorities);
adminDetails.setId(loginInfoByUserName.getId());
return adminDetails;
}
}
四、Security框架对权限的判断和调用
如何获取springsecurity上下文中信息
//通过过滤器获取从前端用户登录的信息
public CsmallAuthenticationInfo getUserInfo() {
UsernamePasswordAuthenticationToken authenticationToken =
(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext()
.getAuthentication();
}
@EnableGlobalMethodSecurity(prePostEnabled = true)在配置类上添加启动
@PreAuthorize("hasAuthority('/ams/admin/read')")在controller上添加,开启方法权限判断