一.首先说一下 表
1.用户表
2.用户角色表
3.角色表
4.角色权限表
5.权限表(包含菜单表)
二.整个逻辑如下:
1. 一个后台可能多个用户 (用户表)
用户角色表连接 1 - 2
2.每一个用户可能用户多个角色,例如,AA用户 既是超级管理员 又是客服管理 (角色表)
角色权限表 连接 2 - 3
3.每个角色又有多种权限 ,例如,超级管理员,可以查看权限列表,后台客服管理又可以浏览日志模块,(权限表)
三 .表字段
用户表: 基本字段 + ids(角色表id集合)
角色表: id ,角色名称, 角色代码,描述,创建角色id, ids,权限ids
用户角色表: 用户id ,角色id
权限表: id,权限名称,权限代码,icon ,权限path, 排序sort. 菜单,父parentId
角色权限表: 角色id,权限id
四.JWT实现,引入JWT maven依赖,
io.jsonwebtoken
jjwt
0.6.0
org.apache.commons
commons-lang3
3.4
org.springframework.boot
spring-boot-starter-security
JWTutil
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
/**
* JWT安全工具类
*
* @ClassName: JwtUtil
* @author xie
* @date 2019年1月10日
*/
@ConfigurationProperties("jwt.config")
@Component
public class JwtUtil {
private String key;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
/**
* 生成JWT
*
* @param id
* @param subject
* @return
*/
public String createJWT(String id, String subject, String headPic, String roles, long ttl) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder().setId(id).setSubject(subject).setIssuedAt(now)
.signWith(SignatureAlgorithm.HS256, getKeyInstance(key)).claim("roles", roles)
.claim("headPic", headPic);
if (ttl > 0) {
builder.setExpiration(new Date(nowMillis + ttl));
}
return builder.compact();
}
/**
* 获取生成token的key
*
* @return
*/
private static Key getKeyInstance(String key) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(key);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
return signingKey;
}
/**
* 解析JWT
*
* @param jwtStr
* @return
*/
public Claims parseJWT(String jwtStr) {
return Jwts.parser().setSigningKey(getKeyInstance(key)).parseClaimsJws(jwtStr).getBody();
}
/**
* 获取用户ID
*
* @return
*/
public Long getUserId(Claims claims) {
return Long.parseLong(claims.get("jti").toString());
}
/**
* 刷新TOKEN
*
* @param refreshClaims
* @param ttl
* @param refttl
* @return
*/
public Map refreshToken(Claims refreshClaims, Long ttl, Long refttl) {
String id = refreshClaims.getId();
String subject = refreshClaims.getSubject();
String roles = refreshClaims.get("roles").toString();
String headPic = refreshClaims.get("headPic").toString();
// 重新生成TOKEN
String accessToken = createJWT(id, subject, headPic, roles, ttl);
String refreshToken = createJWT(id, subject, headPic, roles, refttl);
Map tokenMap = new HashMap<>();
tokenMap.put("accessToken", accessToken);
tokenMap.put("refreshToken", refreshToken);
return tokenMap;
}
}
代码思路:
一。登陆
前端传入User信息到controller
调用用户服务查询当前用户
根据账户查询用户表,
如果不存在,返回账户密码错误
获取当前第一个用户
校验密码,如果该密码为空,或者传进来的用户密码与数据库的密码不一致,不等,返回帐户密码错误
校验状态,如果查到的用户状态为0,请联系管理员
查询角色列表,根据用户id查询当前用户拥有多少角色,
如果角色未空,返回该用户未分配角色
如果不为空,(可以判断该角色是否满足当前权限,如果不满足,返回权限不足)
创建authToken map。
创建访问TOKEN
创建刷新TOKEN
将访问和刷新token 放入map。
给查到当前的用户设置刷新时间
更新用户表
根据用户id 加载权限列表
-------------------------------------------------------
从缓存中获取用户权限列表
如果权限为空
获取用户的角色
如果角色中包含超级管理员,查询所有可用权限
否则查询用户制定全选
将权限放入redis
设置过期时间 优化缓存(2小时)
返回权限
-------------------------------------------------------
如果返回结果为ture
获取authtoken。
将访问token设置到相应头中,key,salt,accesstoken,
将刷新token设置到相应头中,refrekey ,refreshtoken。
返回结果。
因为我们用了security 加密密码。所以在设置拦截器的时候,要先放行。security的所有请求。
启动类注入Spring Security的 密码加密
@Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* Security 配置类
* @ClassName: SecurityConfig
* @author sans
* @date 2019年1月11日
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
System.out.println("进入放权所有请求");
http.authorizeRequests().antMatchers("/**").permitAll().anyRequest().authenticated().and().csrf().disable();
}
@Override
public void configure(WebSecurity web)throws Exception{
System.out.println("进入放权所有静态页面");
web.ignoring()
.antMatchers("**/favicon.ico")
.antMatchers("**/webjars/**")
.antMatchers("**/v2**")
.antMatchers("**/swagger-resources/**")
.antMatchers("**/resources/**")
.antMatchers("/swagger-resources/configuration/ui")
.antMatchers("/swagger-resources")
.antMatchers("/swagger-resources/configuration/security")
.antMatchers("**/swagger-ui.html/**");
System.out.println("进入放权所有静态页面======结束");
}
}
并添加拦截器
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.qiluodz.interceptor.SyncPermissionInteceptor;
import com.qiluodz.interceptor.WebInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.util.ArrayList;
import java.util.List;
/**
* 配置类
*
* @ClassName: FastJsonHttpMessageConfig
* @author xie
* @date 2019年1月9日
*/
@SpringBootConfiguration
public class FastJsonHttpMessageConfig extends WebMvcConfigurationSupport {
@Autowired
private WebInterceptor webInterceptor; //web拦截器
@Autowired
private SyncPermissionInteceptor syncPermissionInteceptor; //同步权限拦截器
/**
* 配置fastjson
*/
@Override
public void configureMessageConverters(List> converters) {
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
// 添加fastJson 的配置信息
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue,
SerializerFeature.WriteDateUseDateFormat);
// 处理中文乱码问题
List fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fastConverter.setSupportedMediaTypes(fastMediaTypes);
// 在convert中添加配置信息.
fastConverter.setFastJsonConfig(fastJsonConfig);
// 将convert添加到converters当中
converters.add(fastConverter);
}
/**
* 配置拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 放行登录注册
registry.addInterceptor(webInterceptor).addPathPatterns("/**");
registry.addInterceptor(syncPermissionInteceptor).addPathPatterns("/**");
// 放行swagger
registry.addInterceptor(webInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/user/login")
.excludePathPatterns("**/swagger-resources/**", "**/webjars/**", "**/v2/**", "**/swagger-ui.html");
registry.addInterceptor(syncPermissionInteceptor)
.addPathPatterns("/**")
.excludePathPatterns("/user/login")
.excludePathPatterns("**/swagger-resources/**", "**/webjars/**", "**/v2/**", "**/swagger-ui.html");
}
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("**/swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
---------------------------------------------------------------------------------------
web拦截器
import com.alibaba.fastjson.JSONObject;
import com.qiluodz.pojo.vo.DzResult;
import com.qiluodz.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* WEB过滤器
*
* @ClassName: WebInteceptor
* @author xie
* @date 2019年1月9日
*
*/
@Component
public class WebInterceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(WebInterceptor.class);
@Autowired
private JwtUtil jwtUtil;
@Value("${auth.key}")
private String key;
@Value("${auth.refrekey}")
private String refrekey;
@Value("${auth.salt}")
private String salt;
@Value("${auth.ttl}")
private Long ttl;
@Value("${auth.refrettl}")
private Long refttl;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("_____进入WebInterceptor_______");
System.out.println("========当前请求路径为=======");
System.out.println(request.getRequestURL().toString());
// 配置跨域
System.out.println("拦截所有请求");
System.out.println("设置请求头");
String origin = request.getHeader("Origin");
if (origin != null && !"".equals(origin)) {
System.out.println("获取头,请求头头不等于空");
System.out.println("开始设置相应头");
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Max-Age", "3600");
response.addHeader("allowCredentials", "true");
response.addHeader("Access-Control-Expose-Headers", "Authorization, RefreAuth");
if (request.getMethod().equalsIgnoreCase("OPTIONS")) {
System.out.println("请求头中有options,忽略大小写");
System.out.println("设置响应头的方法");
response.addHeader("Access-Control-Allow-Methods", "GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,PATCH");
response.addHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization, RefreAuth");
}
}
if (request.getRequestURL().toString().contains("/api/user/login")) {
System.out.println("访问的是登录页面,放行");
return true;
}
//-----sans
if (request.getRequestURL().toString().contains("/api/customer/loginByUserName")) {
System.out.println("2.进入前端登录页面,放行");
return true;
}
if (request.getRequestURL().toString().contains("/swagger")) {
System.out.println("3.进入SWAGGER-UI.html.放行");
return true;
}
if (request.getRequestURL().toString().contains("/webjars")) {
System.out.println("3.进入SWAGGER-UI.html.放行");
return true;
}
// 验权
String token = request.getHeader("Authorization");
System.out.println("开始验权限");
Claims claims = null;
if (StringUtils.isNotBlank(token)) {
System.out.println("token不等于空");
System.out.println("token+个盐");
token = token.substring(salt.length() + 1);
if (StringUtils.isNotBlank(token)) {
System.out.println("token加盐后再次判断,不为空");
try {
// 解析token
System.out.println("JWT解析Token");
claims = jwtUtil.parseJWT(token);
request.setAttribute("authInfo", claims);
System.out.println("解析成功,授权该请求");
return true;
} catch (Exception e1) {
System.out.println("jwt,解析失败");
// 验权失败,获取刷新TOKEN
System.out.println("开始刷新token,");
try {
System.out.println("头已经存在,已登录的");
String refreToken = request.getHeader("RefreAuth");
System.out.println("在请求头中,获取刷新token");
Claims refreshClaims = jwtUtil.parseJWT(refreToken);
System.out.println("解析刷新头");
Map tokenMap = jwtUtil.refreshToken(refreshClaims, ttl, refttl);
// 设置请求和响应信息
request.setAttribute("authInfo", refreshClaims);
System.out.println("授权");
System.out.println("获取koken并加盐,然后设置到相应头中");
response.setHeader(key, salt + " " + tokenMap.get("accessToken"));
System.out.println("获取刷新头,并设置到相应头中");
response.setHeader(refrekey, tokenMap.get("refreshToken"));
logger.info("TOKEN在拦截器一刷新");
System.out.println("__________________________________结束WebInterceptor__________________________________");
return true;
} catch (Exception e2) {
System.out.println("刷新头不存在,首次登陆");
e2.printStackTrace();
}
}
}
}
System.out.println("token不存在,或者为空,验签失败");
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter().append(JSONObject.toJSONString(new DzResult().error(2002, "验签失败")));
return false;
}
}
---------------------------------------------------------------------------------------
同步权限拦截器
import com.alibaba.fastjson.JSONArray;
import com.qiluodz.exception.AuthenticationException;
import com.qiluodz.pojo.entity.DzUser;
import com.qiluodz.service.managerService.RoleService;
import com.qiluodz.service.managerService.UserService;
import com.qiluodz.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 同步权限拦截器
*
* @ClassName: ReloadPermissionInteceptor
* @author xie
* @date 2019年1月15日
*/
@Component
@SuppressWarnings({ "rawtypes", "unchecked" })
public class SyncPermissionInteceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(SyncPermissionInteceptor.class);
@Autowired
private JwtUtil jwtUtil;
@Value("${auth.ttl}")
private Long ttl;
@Value("${auth.refrettl}")
private Long refttl;
@Value("${auth.key}")
private String key;
@Value("${auth.refrekey}")
private String refrekey;
@Value("${auth.salt}")
private String salt;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Value("${PERMISSION_LIST_KEY}")
private String permissionListKey;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("=============进入同步权限拦截器===============");
System.out.println("========当前请求路径为============");
System.out.println(request.getRequestURL().toString());
System.out.println("=========== ===============");
if (request.getRequestURL().toString().contains("/api/user/login")) {
System.out.println("1.进入登录页面,放行");
return true;
}
//-----sans
if (request.getRequestURL().toString().contains("/api/customer/loginByUserName")) {
System.out.println("2.进入前端登录页面,放行");
return true;
}
if (request.getRequestURL().toString().contains("/swagger")) {
System.out.println("3.进入SWAGGER-UI.html.放行");
return true;
}
if (request.getRequestURL().toString().contains("/webjars")) {
System.out.println("3.进入SWAGGER-UI.html.放行");
return true;
}
// 获取当前用户
Claims claims = (Claims) request.getAttribute("authInfo");
if (claims!=null){
System.out.println("2.获取当前用户");
System.out.println("---------------------------");
System.out.println("claims:__"+claims);
System.out.println("___________________________");
}
Long uid = Long.parseLong(claims.getId());
// 查询权限缓存
String permissionJson = (String) redisTemplate.boundValueOps(permissionListKey + uid).get();
System.out.println("查询权限缓存");
if (StringUtils.isBlank(permissionJson)) {
System.out.println("权限不存在");
// 重新加载权限
System.out.println("重新加载权限,获取当前用户的所有权限");
String loadPermission = userService.loadPermissionByUserId(uid);
if (StringUtils.isBlank(loadPermission)) {
System.out.println("权限为空,权限不足");
throw new AuthenticationException(403, "权限不足");
}
// 刷新角色及TOKEN
DzUser currentUser = userService.findUserById(uid);
System.out.println("获取最新角色和token");
if (currentUser == null) {
System.out.println("用户不存在,权限不足");
throw new AuthenticationException(403, "权限不足");
}
List roles = roleService.findRoleListByUser(uid);
System.out.println("查询角色表中是否有该用户");
if (roles == null || roles.size() <= 0) {
System.out.println("没有.权限不足");
throw new AuthenticationException(403, "权限不足");
}
System.out.println("满足以上条件,创建访问token");
// 创建访问TOKEN
String accessToken = jwtUtil.createJWT(currentUser.getId() + "", currentUser.getUsername(),
currentUser.getHeadPic(), JSONArray.toJSONString(roles), ttl);
// 创建刷新TOKEN
System.out.println("创建刷新token");
String refreshToken = jwtUtil.createJWT(currentUser.getId() + "", currentUser.getUsername(),
currentUser.getHeadPic(), JSONArray.toJSONString(roles), refttl);
Claims reloadClaims = jwtUtil.parseJWT(accessToken);
System.out.println("授权访问.");
request.setAttribute("authInfo", reloadClaims);
response.setHeader(key, salt + " " + accessToken);
response.setHeader(refrekey, refreshToken);
logger.info("TOKEN在拦截器二刷新,用户ID[" + claims.getId() + "] 用户名[" + claims.getSubject() + "]");
}
System.out.println("权限存在,放行");
return true;
}
}
---------------------------------------------------------------------------------------
创建用户controller
注入请求与相应
@Autowired
private HttpServletResponse response;
@Autowired
private HttpServletRequest request;
@Value("${auth.key}")
private String key;
@Value("${auth.refrekey}")
private String refrekey;
@Value("${auth.salt}")
private String salt;
---------------- 用户登陆 -----------------------------------------------------------------------
在登录时,根据用户服务,判断当前用户登录账户及密码正确
/**
* 用户登录
*
* @param user
* @return
*/
@PostMapping("/login")
@ApiOperation("用户登录")
@SuppressWarnings("unchecked")
public DzResult login(@RequestBody DzUser user) {
DzResult result = userService.findUserByUsernameAndPassword(user);
if (result.getFlag() == true) {
Map authToken = (Map) result.getData();
String accessToken = authToken.get("accessToken");
String refreshToken = authToken.get("refreshToken");
response.setHeader(key, salt + " " + accessToken);
response.setHeader(refrekey, refreshToken);
result.setData(null);
}
return result;
}
@Override
public DzResult findUserByUsernameAndPassword(DzUser user) {
DzUserExample example = new DzUserExample();
Criteria criteria = example.createCriteria();
criteria.andUsernameEqualTo(user.getUsername());
List userList = userMapper.selectByExample(example);
if (userList == null || userList.size() <= 0) {
return new DzResult().error(StatusCode.LOGIN_ERROR, "用户名或密码错误");
}
DzUser existUser = userList.get(0);
// 校验密码
if (StringUtils.isBlank(user.getPassword())
|| !passwordEncoder.matches(user.getPassword(), existUser.getPassword())) {
return new DzResult().error(StatusCode.LOGIN_ERROR, "用户名或密码错误");
}
// 校验状态
// 状态 1:正常使用 0:禁用
if (existUser.getStatus().equals("0")) {
return new DzResult().error(StatusCode.LOGIN_USER_PROHIBIT, "账户已被禁用,请联系管理员");
}
// 查询角色列表
List roles = roleMapper.findRolesByUser(existUser.getId());
if (roles == null || roles.size() <= 0) {
return new DzResult().error(StatusCode.LOGIN_ROLE_NONE, "用户未分配角色");
} else {
if (roles.contains("MANUFACTURER") || roles.contains("SURVEYOR") ) {
System.out.println("该账户权限不足,请联系客服");
throw new AuthenticationException(403, "权限不足,该账户是厂商/量体,请联系客服");
}
}
Map authToken = new HashMap<>();
// 创建访问TOKEN
String accessToken = jwtUtil.createJWT(existUser.getId() + "", existUser.getUsername(), existUser.getHeadPic(),
JSONArray.toJSONString(roles), ttl);
// 创建刷新TOKEN
String refreshToken = jwtUtil.createJWT(existUser.getId() + "", existUser.getUsername(), existUser.getHeadPic(),
JSONArray.toJSONString(roles), refttl);
authToken.put("accessToken", accessToken);
authToken.put("refreshToken", refreshToken);
// 刷新登录时间
existUser.setLoginTime(new Date());
userMapper.updateByPrimaryKey(existUser);
// 加载权限列表
loadPermissionByUserId(existUser.getId());
return new DzResult().success(authToken);
}
--------------------------------------------------------------------------
userRoleMapper
--------------------------------------------------------------------------
rolerMapper
--------------------------------------------------------------------------
PermissionMapper
--------------------------------------------------------------------------
RolePermissionMapper
--------------------------------------------------------------------------
代码思路 -- 添加用户
密码加密,补全属性,设置头像
关联角色信息,如果用户传过来的角色id为空,抛异常,绑定错误
切割所有角色id,便利
创建关联对象,设置角色id & 设置用户id,插入关联对象表
代码思路 - - 更新用户
查到当前用户,
更新用户信息,执行更新
获取用户所有角色ID , 如果角色id为空,授权失败
查找已存在的用户id的绑定的所有角色 清空角色,
获取,当前更新的用户的 所有角色.
便利获取每个角色id,
创建用户角色关联对象,
设置用户id和角色id .
插入角色表,
调用清除权限缓存
代码思路 - - 删除用户
获取删除用户的 id集合
便利,获取每一个用户
设置伪删除,设置更新时间
更新.
调用清除权限缓存
--------------------------------------------------------------------------------------------------------------------------