这里是接上一篇,万字长文 基于SpringBoot整合SpringSecurity的认证授权(角色+权限) 真案列、有数据库、有源码,上一篇写的很细但是不是前后端分离,这一篇把前后端分离补上,数据库和Mapper模块都和前面的一致就不累述了,以下都是我自己的理解,如果什么地方有问题,请各位前辈指出,感激不尽。
其实变化也不大,就新增了拦截器和Service,以及一个工具类,当然配置文件中变换还是蛮大的,
嗯,在本文中会用到Jwt,关于Jwt嗯,因为我学习的时候没有写文章,后面可能会出一篇,好吧又给自己挖一个坑,这里我就默认大家会JWT了,当然这个项目的源码我也会在后面分享出来的。
首先是编写Jwt工具类,如下
package work.zx.unitl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import work.zx.entity.UserVo;
import java.util.Date;
import java.util.List;
public class JwtUtil {
// 主题
private static final String SUBJECT = "zx";
// jwt的token有效期,
//private static final long EXPIRITION = 1000L * 60 * 60 * 24 * 7;//7天
private static final long EXPIRITION = 1000L * 60 * 30; // 半小时
// 加密key(黑客没有该值无法篡改token内容)
private static final String APPSECRET_KEY = "zxdc";
// 用户url权限列表key
private static final String AUTH_CLAIMS = "auth";
/**
* TODO 生成token
*
* @param user
* @return java.lang.String
* @date 2020/7/6 0006 9:26
*/
public static String generateToken(UserVo user) {
String token = Jwts
.builder()
// 主题
.setSubject(SUBJECT)
// 添加jwt自定义值
.claim(AUTH_CLAIMS, user.getAutk())
.claim("username", user.getUsername())
.claim("userId", user.getUid())
.setIssuedAt(new Date())
// 过期时间
.setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))
// 加密方式,加密key
.signWith(SignatureAlgorithm.HS256, APPSECRET_KEY).compact();
return token;
}
/**
* 获取用户Id
*
* @param token
* @return
*/
public static String getUserId(String token) {
Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
return claims.get("userId").toString();
}
/**
* 获取用户名
*
* @param token
* @return
*/
public static String getUsername(String token) {
Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
return claims.get("username").toString();
}
/**
* 获取用户角色的权限列表, 没有返回空
*
* @param token
* @return
*/
public static List<SimpleGrantedAuthority> getUserAuth(String token) {
Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
List auths = (List) claims.get(AUTH_CLAIMS);
String json = JSONArray.toJSONString(auths);
return JSON.parseArray(json, SimpleGrantedAuthority.class);
}
/**
* 验证是否过期
*
* @param token
* @return
*/
public static boolean isExpiration(String token) {
Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
System.out.println("过期时间: " + claims.getExpiration());
return claims.getExpiration().before(new Date());
}
}
这个不是必须的,也可以自己写一个登录接口,然后认证成功后将权限信息生成Token传给前端,我项目中就是使用的这种方式,这个dome我还是把登录拦截器弄出来了。
源码如下,
package work.zx.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.servlet.HandlerExceptionResolver;
import work.zx.dao.PermissionMapper;
import work.zx.dao.RoleMapper;
import work.zx.dao.UserMapper;
import work.zx.entity.Permission;
import work.zx.entity.Role;
import work.zx.entity.UserVo;
import work.zx.filter.JWTLoginFilter;
import work.zx.filter.JWTValidFilter;
import work.zx.service.AuthenticationProviderImpl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
//用户表
@Autowired
private UserMapper userMapper;
//角色表
@Autowired
private RoleMapper roleMapper;
//资源表
@Autowired
private PermissionMapper permissionMapper;
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
@Autowired
private AuthenticationProviderImpl authenticationProvider;
/**
* TODO 授权
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry eiur = http.authorizeRequests();
eiur
// 登录接口不需要权限控制,可删除,目前该接口不在权限列表中
.antMatchers("/auth/login", "POST").permitAll()
// 设置JWT过滤器
.and()
.addFilter(new JWTValidFilter(authenticationManager(), resolver))
.addFilter(new JWTLoginFilter(authenticationManager(), resolver)).csrf().disable()
// 剔除session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 开启跨域访问
http.cors().disable();
// 开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误
http.csrf().disable();
// iframe 跳转错误处理 Refused to display 'url' in a frame because it set 'X-Frame-Options' to 'deny'
http.headers().frameOptions().disable();
}
}
当然这里是我这样叫,这里是拿到请求中携带的token,将token中权限部分取出来,然后保存到Security的Authentication授权管理器中,给我们后面注入那个AuthenticationProviderImpl,
package work.zx.filter;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.SignatureException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerExceptionResolver;
import work.zx.unitl.JwtUtil;
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.List;
public class JWTValidFilter extends BasicAuthenticationFilter {
// 异常处理类
private HandlerExceptionResolver resolver;
/**
* SecurityConfig 配置中创建该类实例
*/
public JWTValidFilter(AuthenticationManager authenticationManager, HandlerExceptionResolver resolver) {
// 获取授权管理
super(authenticationManager);
// 获取异常处理类
this.resolver = resolver;
}
/**
* 拦截请求
*
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// 获取token, 没有token直接放行
System.out.println("开始授权验证");
String token = request.getHeader("token");
if (StringUtils.isEmpty(token) || "null".equals(token)) {
super.doFilterInternal(request, response, chain);
// 获取token, 没有token直接放行
return;
}
// 有token进行权限验证
List<SimpleGrantedAuthority> userAuthList = null;
String username = null;
try {
// 权限列表
userAuthList = JwtUtil.getUserAuth(token);
// 获取账号
username = JwtUtil.getUsername(token);
} catch (SignatureException ex) {
resolver.resolveException(request, response, null, new Exception( "JWT签名与本地计算签名不匹配"));
return;
} catch (ExpiredJwtException ex) {
resolver.resolveException(request, response, null, new Exception( "登录过期"));
return;
} catch (Exception e) {
resolver.resolveException(request, response, null, new Exception( "JWT解析错误"));
return;
}
// 添加账户的权限信息,和账号是否为空,然后保存到Security的Authentication授权管理器中
if (!StringUtils.isEmpty(username) && userAuthList != null) {
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(username, null, userAuthList));
}
super.doFilterInternal(request, response, chain);
}
}
这个类实现了AuthenticationProvider接口,这个接口就两个方法
Authentication authenticate(Authentication authentication) throws AuthenticationException;
源码注释中说 这个是做身份认证的,传入一个身份验证请求对象。当然这些方法的调用就是SpringSecurity自己的事儿了,我们只需要重写即可使用,其实我自己也还没弄清楚。
package work.zx.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import work.zx.dao.PermissionMapper;
import work.zx.dao.RoleMapper;
import work.zx.dao.UserMapper;
import work.zx.entity.Permission;
import work.zx.entity.Role;
import work.zx.entity.UserVo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Component
public class AuthenticationProviderImpl implements AuthenticationProvider {
//用户表
@Autowired
private UserMapper userMapper;
//角色表
@Autowired
private RoleMapper roleMapper;
//资源表
@Autowired
private PermissionMapper permissionMapper;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//拿到登录传过来的信息
String username = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
//存放权限的
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
UserVo userVo = userMapper.selectBYUserName(username);
if (userVo == null || !password.equals(userVo.getPassword())) {
System.out.println("用户名或者密码输入错误");
throw new UsernameNotFoundException("用户名或者密码输入错误");
}
//拿到用户角色
List<Role> roles = roleMapper.selectByUserId(userVo.getUid());
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
//拿到用户具有的权限
List<Permission> permissionList = permissionMapper.selectByRoleId(role.getRid());
for (Permission permission : permissionList) {
//添加到
authorities.add(new SimpleGrantedAuthority(permission.getStr()));
}
}
userVo.setAutk(authorities);
authentication = new AbstractAuthenticationToken(new ArrayList<>()) {
@Override
public Object getCredentials() {
return userVo.getPassword();
}
@Override
public Object getPrincipal() {
return userVo;
}
};
authentication.setAuthenticated(true);
return authentication;
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
package work.zx.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.servlet.HandlerExceptionResolver;
import work.zx.dao.PermissionMapper;
import work.zx.dao.RoleMapper;
import work.zx.dao.UserMapper;
import work.zx.entity.Permission;
import work.zx.entity.Role;
import work.zx.entity.UserVo;
import work.zx.filter.JWTLoginFilter;
import work.zx.filter.JWTValidFilter;
import work.zx.service.AuthenticationProviderImpl;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
@Autowired
private AuthenticationProviderImpl authenticationProvider;
/**
* TODO 授权
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry eiur = http.authorizeRequests();
eiur
// 登录接口不需要权限控制,可删除,目前该接口不在权限列表中
.antMatchers("/auth/login", "POST").permitAll()
// 设置JWT过滤器
.and()
.addFilter(new JWTValidFilter(authenticationManager(), resolver))
.addFilter(new JWTLoginFilter(authenticationManager(), resolver)).csrf().disable()
// 剔除session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 开启跨域访问
http.cors().disable();
// 开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误
http.csrf().disable();
// iframe 跳转错误处理 Refused to display 'url' in a frame because it set 'X-Frame-Options' to 'deny'
http.headers().frameOptions().disable();
}
}
暂时就先到这里就结束了,后面等我更近一步会更新的。源码在下面。
链接:https://pan.baidu.com/s/1vuYW_dquZoVmPwWL2u7j1g
提取码:m18u