一、RBAC-MANAGER(用户权限管理)
1.pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xgj</groupId>
<artifactId>rbac-test-01</artifactId>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.application.yml
server:
port: 8082
servlet:
context-path: /rbac
spring:
datasource:
url: jdbc:mysql://localhost:3306/incodedb
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
jackson:
date-format: yyyy-MM-dd
mybatis-plus:
global-config:
db-config:
id-type: auto
table-prefix: t_
logging:
level:
com.xgj.mapper: debug
Security
1.LoginUserDetailService
@Component
public class LoginUserDetailService implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private AuthService authService;
//用户登录的时候调用,根据用户输入的用户名去查询密码
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("--------------------LoginUserDetailService-------------------------");
System.out.println("用户【"+username+"】正在做登录操作。。。。");
//1.根据用户名查询密码
User user= userService.getUserByUsername(username);
//2.判断用户是否存在
if (user == null) {
throw new RbacException(1001,"用户不存在");
}
//>查询当前用户的角色
List<String> roleNameList=authService.getRoleListByUid(user.getId());
//.3创建一个的登录的User对象
SecurityUser securityUser=new SecurityUser();
securityUser.setCurrentUser(user);
securityUser.setRoleNameList(roleNameList);
//4.返回SecurityUser,当时候SpringSecurity调用这个类中getPassword()来完成密码的比对
return securityUser;
}
}
2.JwtTokenFilter
@Component
public class JwtTokenFilter extends OncePerRequestFilter {
@Autowired
private LoginUserDetailService userDetailService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//1.获取Token
String token = request.getHeader(SecurityConstant.TOKEN_NAME);
//2.检验token
Claims claim = JwtUtil.getClaim(token);
if (claim == null) {
Result error=Result.error();
error.put("code",501);//501代表Token有误
error.put("msg","没有携带token或token过期");
ResponseUtil.out(response, error);
return;
}
//3.token检验成功后从token中获取主体信息
String username = claim.get(SecurityConstant.USER_NAME, String.class);
List roleName= claim.get("roleName", List.class);
//4.根据用户名查询用户信息
SecurityUser securityUser = (SecurityUser) userDetailService.loadUserByUsername(username);
List<String> roleNameList = securityUser.getRoleNameList();
//5.封装
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(securityUser,null,securityUser.getAuthorities());
//6.全局登录设置
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//7.放行
filterChain.doFilter(request,response);
}
}
3.UrlAuthenFilter
@Component
public class UrlAuthenFilter {
@Autowired
private PermissionService permissionService;
@Autowired
private AntPathMatcher antPathMatcher;
@Autowired
private AuthService authService;
/**
*@Author xiaogongjue
*@Date2021/10/11 21:46
*@Description request当前的请求 authentication主体
*/
public boolean checkUrlAuthen(HttpServletRequest request, Authentication authentication){
//1.定义一个标识,如果有权限为true
boolean flag=false;
//2.获取当前的请求
String reqUrl = request.getRequestURL().toString();
//3.获取Db中的全部URL
List<Permission> dbUrlList = permissionService.allUrlList();
//4.遍历dbUrllIst与当前的请求的Url----》urlId---->角色
for (Permission permission : dbUrlList) {
if(antPathMatcher.match(permission.getUrl(),reqUrl)){
//6.根据菜单id查询角色(访问这些url需要这些的角色)
List<String> roleCodes= authService.getRoleCodeByPid(permission.getId());
//7.判断当前主体是否有这写角色的一个
SecurityUser securityUser= (SecurityUser) authentication.getPrincipal();
List<String> userRoleNameList = securityUser.getRoleNameList();
for (String roleCode : userRoleNameList) {
//如果是包含关系就是有权限
if(roleCodes.contains(roleCode)){
flag=true;
}
}
}
}
return flag;
}
4.UserAccessDeniedHandler用户没有权限处理器
@Component
public class UserAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
Result error=Result.error();
error.put("code",502);//502代表没有权限
error.put("msg","你没有权限访问。。");
ResponseUtil.out(httpServletResponse, error);
}
}
5.UserAuthenticationEntryPoint用户没有登录(授权)
@Component
public class UserAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
ResponseUtil.out(httpServletResponse, Result.error("你还没有登录,请先登录"));
}
}
6.UserLoginFilter用户登录的过滤器
@Component
public class UserLoginFilter extends UsernamePasswordAuthenticationFilter {
@Autowired//从IOC容器注入authenticationManager给父类初始化
@Override
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
//认证成功时调用
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
//1.当前登录的主体
SecurityUser securityUser = (SecurityUser) authResult.getPrincipal();
System.out.println("欢迎:【"+securityUser+"】登录成功");
//2.定义需要放入Token中的数据
Map<String,Object> map=new HashMap<>();
map.put(SecurityConstant.USER_NAME,securityUser.getUsername());
map.put("roleName",securityUser.getRoleNameList());
//3.创建Token
String token = JwtUtil.createToken(map);
//4.响应登录成功
ResponseUtil.out(response,Result.putDate(SecurityConstant.TOKEN_NAME,token));
}
//认证失败后调用
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
ResponseUtil.out(response, Result.error("用户名或密码错误,请你重试~~"));
}
}
mybatis-plus
1.UserMetaHandler(数据库自动填充)
/**
* @Author xiaogongjue
* @Date2021/10/6 12:56
* @Description 自动填字段
*/
@Component
public class UserMetaHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
System.out.println("字段自动填充");
Object status = getFieldValByName("status", metaObject);
if (status == null) {
setFieldValByName("status", 1, metaObject);
}
Object createTime = getFieldValByName("createTime", metaObject);
if (createTime == null) {
setFieldValByName("createTime", new Date(), metaObject);
}
}
@Override
public void updateFill(MetaObject metaObject) {
}
}
2.common包下新建拣utils
工具类Result
public class Result extends HashMap<String, Object> {
public Result() {
//0代表成功,非0代表失败
super.put("code", 0);
super.put("msg", "success");
}
public static Result ok() {
//响应成功
return new Result();
}
//响应成功带有成功的提示信息
public static Result ok(String msg) {
Result r = new Result();
r.put("msg", msg);
return r;
}
//响应失败
public static Result error() {
Result r = new Result();
r.put("code", 500);
r.put("msg", "error");
return r;
}
//响应失败带有失败的原因信息
public static Result error(String msg) {
Result r = Result.error();
r.put("msg", msg);
return r;
}
//对成功与否的判断
public static Result out(Object value) {
if (value instanceof Boolean) {//对操作结果是Boolean类型的操作,比例更新,删除
if ((boolean) value) {
return Result.ok();
} else {
return Result.error();
}
}else if(value instanceof Integer){//对操作结果是Integer类型的操作,比例插入
if((Integer)value>0){
return Result.ok();
}else{
return Result.error();
}
}
return Result.error();
}
//将数据以键值对的形式存起来
public static Result putDate(String key,Object object){
Result r=new Result();
r.put(key,object);
return r;
}
}
public class ResponseUtil {
public static void out(HttpServletResponse response,Result r){
//设置响应的数类型为JSON,编码格式为UTF-8
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("utf-8");
try {
//将Result对象转成JSON字符串写出去
response.getWriter().write(JSON.toJSONString(r));
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class JwtUtil {
private static final String sign = "2105-rbac"; // 签名
private static final long EXPIRE = 1000 * 60 * 30; // 过期时间 30m
/**
* 生成token
*
* @return
*/
public static String createToken(Map<String, Object> map) {
Date nowDate = new Date();
//过期时间
Date expireDate = new Date(nowDate.getTime() + EXPIRE);
return Jwts.builder()
.setClaims(map) // 载荷
.setIssuedAt(nowDate) // 当前时间
.setExpiration(expireDate) // 过期时间
.signWith(SignatureAlgorithm.HS512, sign) // 签名
.compact();
}
/**
* 解析Claims
*
* @param token
* @return
*/
public static Claims getClaim(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(sign)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
System.out.println("token解析失败");
}
return claims;
}
/**
* 验证token是否失效
*
* @param token
* @return true:过期 false:没过期
*/
public static boolean isExpired(String token) {
try {
final Date expiration = getExpiration(token);
return expiration.before(new Date());
} catch (ExpiredJwtException expiredJwtException) {
return true;
}
}
/**
* 获取jwt失效时间
*/
public static Date getExpiration(String token) {
return getClaim(token).getExpiration();
}
}
config包下
/**
* @Author xiaogongjue
* @Date2021/10/11 11:31
* @Description 跨越与拦截器
*/
@Component
public class CORSFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, HEAD");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "*");
filterChain.doFilter(servletRequest,response);
}
}
/**
* @Author xiaogongjue
* @Date2021/10/6 11:26
* @Description 配置拦截器
*/
@Configuration
public class MPconfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//将分页拦截器加入到MP配置中
return interceptor;
}
}
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private LoginUserDetailService loginUserDetailService;
//给IOC容器中放入一个密码加密的算法
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//认证过滤器
@Autowired
private UserLoginFilter userLoginFilter;
//没有权限处理器
@Autowired
private UserAccessDeniedHandler userAccessDeniedHandler;
//没有登录(认证)处理器
@Autowired
private UserAuthenticationEntryPoint userAuthenticationEntryPoint;
@Autowired
private JwtTokenFilter jwtTokenFilter;
// 复写父类中的 AuthenticationManager 添加IOC容器中
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(loginUserDetailService) // 设置userDetails
.passwordEncoder(passwordEncoder()); // 设置加密算法
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilter(userLoginFilter)
//jwtTokenFilter要在BasicAuthenticationFilter之前运行
.addFilterBefore(jwtTokenFilter, BasicAuthenticationFilter.class)
.exceptionHandling().accessDeniedHandler(userAccessDeniedHandler)//没有权限处理器
.authenticationEntryPoint(userAuthenticationEntryPoint)//没有认证的处理器
//.and().authorizeRequests().anyRequest().authenticated()//所有资源度需要认证后才能访问
//声明用Token登录
.and().authorizeRequests().anyRequest().access("@urlAuthenFilter.checkUrlAuthen(request,authentication)")
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable()
;
}
}
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// @Override
// public void addCorsMappings(CorsRegistry registry) {
// registry.
// addMapping("/**")
// .allowedMethods("POST","GET","DELETE","PUT")
// .allowedHeaders("*")
// .allowedOrigins("*");
//
//
// }
@Autowired
private CORSFilter corsFilter;
@Bean
public AntPathMatcher antPathMatcher(){
return new AntPathMatcher();
}
@Bean
public FilterRegistrationBean testFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(corsFilter);
registration.addUrlPatterns("/*");
registration.setName("corsFilter");
registration.setOrder(Integer.MIN_VALUE);
return registration;
}
}
entity(权限实体)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Permission implements Serializable {
@TableId
private Integer id;
private String name;
private String url;
private Integer type;
private Integer status;
private Integer pid;//父ID
@TableField(exist = false)
private String pname;//告诉MP表中不存在这个字段
}
权限controller
@RestController
@RequestMapping("/per/")
public class PermissionController {
@Autowired
private PermissionService permissionService;
@PostMapping("save")
public Result savePer(@RequestBody Permission permission){
boolean save = permissionService.save(permission);
return Result.out(save);
}
@GetMapping("/treeList")
public Result treeList(){//后端返回的是简单的JSON数据
return Result.putDate("data", permissionService.treeList());
}
@PostMapping("page")
public Result pagePer(@RequestBody Page<Permission> permissionPage){
Page<Permission> page = permissionService.page(permissionPage);
return Result.putDate("page",page);
}
@GetMapping("delete/{id}")
public Result deletePer(@PathVariable Integer id){
return Result.out(permissionService.removeById(id));
}
@PostMapping("update")
public Result updatePer(@RequestBody Permission permission){
return Result.out(permissionService.updateById(permission));
}
@GetMapping("getById/{id}")
public Result getPerById(@PathVariable Integer id){
Permission permission = permissionService.getAllById(id);
return Result.putDate("permission",permission);
}
}
PermissionMapper
public interface PermissionMapper extends BaseMapper<Permission> {
List<Permission> treeList();
Permission getAllById(Integer id);
List<Permission> allUrlList();
}
PermissionService
public interface PermissionService extends IService<Permission> {
List<Permission> treeList();
Permission getAllById(Integer id);
List<Permission> allUrlList();
}
在resource下新建一个mapper目录,存放PermissionMapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xgj.mapper.PermissionMapper">
<select id="getAllById" resultType="com.xgj.entity.Permission">
SELECT t1.*,t2.`name` AS pname FROM t_permission t1 LEFT JOIN t_permission t2 ON t1.`pid`=t2.`id` WHERE t1.id=#{id}
</select>
<select id="treeList" resultType="com.xgj.entity.Permission">
SELECT t1.*,t2.`name` as pname from t_permission t1 LEFT JOIN t_permission t2 ON (t1.pid=t2.id)
</select>
<select id="allUrlList" resultType="com.xgj.entity.Permission">
SELECT * FROM t_permission p WHERE LENGTH(p.url)>0
</select>
</mapper>