SpringSecurity前后端分离,登录逻辑,基于SpringSecurity实现权限管理系统
Posted 殷丿grd_志鹏
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringSecurity前后端分离,登录逻辑,基于SpringSecurity实现权限管理系统相关的知识,希望对你有一定的参考价值。
先参考文章https://blog.csdn.net/grd_java/article/details/107584578,了解springSecurity基本操作,另外,数据库也在这篇文章创建了 |
---|
文章目录
基本效果说明 |
---|
- 请求login接口,获取token
- 获取token后,通过封装名为Authorization的请求头,值为Bearer+空格+token字符串的形式请求需要授权接口(就是请求时,需要带上authorization:Bearer eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE2Mzk0NjA2MTYsImV4cCI6MTYzOTQ2MTMwMCwic3ViIjoiYWRtaW4iLCJjcmVhdGVkIjoxNjM5NDYwNjE2MTI0fQ._oKG2qarbKi84gxdQjHoRSHd3hx-INQn1CscMgAiASW0B6tsPIjWi1LMr35OTtWR-WvQ8R6tRAvkp0Q3RQm0LQ这样的请求头)
- authorization是请求头key
- Bearer 是我们的一个头标识
- 剩下的就是token字符串
源码,码云 |
---|
@TODO 因为要将springSecurity整个的权限控制流程都涉及到,暂时还没写完,写完会上传到仓库
一、环境说明(太基本的就不讲了,本文主要讲如何前后端分离模式整合Security)
common除了Spring security模块外,都在这篇文章中有讲解https://blog.csdn.net/grd_java/article/details/107452826
不知道怎么建表,请到文章开头的链接中参考
二、配置Security
首先,我们在yml中需要配置一些参数,是我们个人规定好的 |
---|
- 我们规定相应的token 请求头为authorization:Bearer token字符串
jwt:
tokenHeader: Authorization #JWT存储的请求头,请求是这个就是token的key
secret: ukc8BDbRigUDaY6pZFfWus2jZWLPHO #JWT加解密使用的密钥
expiration: 684808 #JWT有效时间(60*60*24)
tokenHead: Bearer #JWT负载中拿到的开头,token字符串,tokenHead是字符串的头,也就是以它开头
1. 用户实体类继承UserDetails接口
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="DdUser对象,使用Spring Security框架就要继承UserDetails接口,实现方法,将返回值改为true", description="")
public class DdUser implements UserDetails
private static final long serialVersionUID=1L;
@ApiModelProperty(value = "id")
@TableId(value = "id", type = IdType.ID_WORKER_STR)
private Integer id;
private String username;
private String password;
/**
* 所有权限
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities()
return null;
/**
* 账号是否过期
* @return
*/
@Override
public boolean isAccountNonExpired()
return true;
/**
* 账号是否被锁定
* @return
*/
@Override
public boolean isAccountNonLocked()
return true;
/**
* 凭证(密码)是否过期
* @return
*/
@Override
public boolean isCredentialsNonExpired()
return true;
/**
* 是否启用
* @return
*/
@Override
public boolean isEnabled()
return true;
2. 编写JWT工具类
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JWT工具类
*/
@Component
public class JwtTokenUtil
private static final String CLAIM_KEY_USERNAME="sub";//荷载,用户名的key
private static final String CLAIM_KEY_CREATED="created";//荷载,创建时间key
/**
* 项目中推荐使用此形式的密钥和过期时间,因为解耦合
*/
@Value("$jwt.secret")
private String secret;//密钥
@Value("$jwt.expiration")
private Long expiration;//过期时间
/**
* 不推荐使用这种,因为写死到了代码中
*/
public static final long EXPIRE = 1000 * 60 * 60 * 24; //设置token过期时间
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; //设置token密钥,我瞎写的,每个公司都有按自己规则生成的密钥
//生成token字符串
/**
* 根据荷载生成token字符
* @param id 密言1
* @param nickname 密言2
* @return
*/
public static String getJwtToken(String id, String nickname)
String JwtToken = Jwts.builder() //构建jwt字符串
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256") //设置jwt头信息
.setSubject("guli-user") //分类,名字随便起的,不同的分类可以设置不同的过期
.setIssuedAt(new Date()) //设置过期时间的计时起始值为当前时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) //设置过期时间为当前时间+EXPIRE我们设定的过期时间
.claim("id", id) //token主体,这里放你需要的信息,我们实现登陆,就放用户登陆信息
.claim("nickname", nickname) //需要多少主体信息,就设置多少个claim属性
.signWith(SignatureAlgorithm.HS256, APP_SECRET) //签名哈希,根据指定规则和我们的密钥设定签名
.compact();
return JwtToken;
/**
* 根据spring security 对象生成token
* @param userDetails UserDetails对象,spring security会将用户信息保存在这个对象
* @return
*/
public String getJwtToken(UserDetails userDetails)
//分组荷载,也就是主体信息,我们将用户名和有效时间封装到token中
Map<String,Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED,new Date());
String JwtToken = gennerateToken(claims);
return JwtToken;
private String gennerateToken(Map<String,Object> claims)
String JwtToken =Jwts.builder() //构建jwt字符串
// .setHeaderParam("typ", "JWT")
// .setHeaderParam("alg", "HS256") //设置jwt头信息
//
// .setSubject("dd-user") //分类,名字随便起的,不同的分类可以设置不同的过期
.setIssuedAt(new Date()) //设置过期时间的计时起始值为当前时间
.setExpiration(new Date(System.currentTimeMillis() + expiration)) //设置过期时间为当前时间+EXPIRE我们设定的过期时间
.addClaims(claims)
// .claim(claims) //token主体,这里放你需要的信息,我们实现登陆,就放用户登陆信息
// .claim() //需要多少主体信息,就设置多少个claim属性
.signWith(SignatureAlgorithm.HS512, secret) //签名哈希,根据指定规则和我们的密钥设定签名
.compact();
return JwtToken;
/**
* 从token中获取登录用户名
*/
public String getUsernameByToken(String token)
String username;
try
Claims claims = getClaimsFormToken(token); //获取主体内容
//claims.get(CLAIM_KEY_USERNAME);
username = claims.getSubject();//获取主体值,上面注释代码和这个都可以,获取的是CLAIM_KEY_USERNAME常量封装的key,对应的值
catch (Exception e)
username = null;
e.printStackTrace();
return username;
/**
* 从token中获取荷载
* @param token
* @return
*/
private Claims getClaimsFormToken(String token)
Claims claims = null;
try
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();//解析token字符串,获取主体内容
catch (Exception e)
e.printStackTrace();
return claims;
/**
* 从荷载中获取过期时间
*/
private Date getExpiredDateFromToken(String token)
Claims claimsFormToken = getClaimsFormToken(token);
return claimsFormToken.getExpiration();
/**
* 验证token是否失效,失效返回true,没失效返回false
*/
private boolean isTokenExpired(String token)
Date expiredDateFromToken = getExpiredDateFromToken(token);
// boolean before = expiredDateFromToken.before(new Date());
return expiredDateFromToken.before(new Date());//如果当前时间再过期时间之前,就是还没有到过期时间,返回true,否则返回false
/**
* 判断token是否有效
* @param jwtToken token字符串
* @return
*/
public boolean checkToken(String jwtToken,UserDetails userDetails)
if(StringUtils.isEmpty(jwtToken)) return false;//如果为空直接返回false表示失效
String username;
try
username = getUsernameByToken(jwtToken);//根据token获取用户名
catch (Exception e)
e.printStackTrace();
username = null;
return false;//获取不到用户名,直接失效
if(!username.equals(userDetails.getUsername())) return false;//如果token的用户名与当前UserDetails用户名不一致,直接失效
if(isTokenExpired(jwtToken)) return false;//如果token失效,返回false
return true;
/**
* 判断token是否可以被刷新
*/
public boolean canRefresh(String token)
return !isTokenExpired(token);//已经过期,表示可以被刷新
/**
* 刷新token
*/
public String refreshToken(String token)
Claims claimsFormToken = getClaimsFormToken(token);
claimsFormToken.put(CLAIM_KEY_CREATED,new Date());
return gennerateToken(claimsFormToken);
3. 配置JWT过滤器
任何请求都需要进行过滤,如果请求携带我们规定的token,那么将token放在spring Security对象中 |
---|
import com.dd.security.utils.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
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.Enumeration;
/**
* JWT登录授权过滤器,继承OncePerRequestFilter
*/
public class JwtAuthencationTokenFilter extends OncePerRequestFilter
@Value("$jwt.tokenHeader")
private String tokenHeader;//JWT存储的请求头
@Value("$jwt.tokenHead")
private String tokenHead;//JWT负载中拿到的开头,token字符串,tokenHead是字符串的头,也就是以它开头
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
/**
* 前置拦截
*/
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException
// Enumeration<String> headerNames = request.getHeaderNames();//debug用的
//通过tokenHeader获取验证头,也就是tokenHead+token字符串
String authHeader = request.getHeader(tokenHeader);
//如果验证头存在,并且token字符串符合我们规定的tokenHead开头
if(authHeader!=null && authHeader.startsWith(tokenHead))
//将token截取出来
String authToken = authHeader.substring(tokenHead.length());
//根据token获取用户名
String username = jwtTokenUtil.getUsernameByToken(authToken);
//如果token存在,但是Security全局上下文没有用户,表示用户没有登录
if(username != null && SecurityContextHolder.getContext().getAuthentication() == null)
//登录
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
//如果token有效
if(jwtTokenUtil.checkToken(authToken,userDetails))
//更新登录用户对象UserDetails
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
//重新设置到用户对象中
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//设置到security全局上下文
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request,response);//放行
4. 自定义返回结果
当访问接口没有权限,自定义返回结果 |
---|
import com.dd.common_utils.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet以上是关于SpringSecurity前后端分离,登录逻辑,基于SpringSecurity实现权限管理系统的主要内容,如果未能解决你的问题,请参考以下文章
SpringSecurity解决跨域问题,在SpringBoot整合SprinSecurity中如何用前后端分离Ajax登录,Ajax登录返回状态200还是近error
Spring boot+Spring security+JWT实现前后端分离登录认证及权限控制
SpringBoot2.0.3 + SpringSecurity5.0.6 + vue 前后端分离认证授权