一、创建数据库表
DROP TABLE IF EXISTS luo_admin
;
CREATE TABLE luo_admin
(
id
bigint(20) NOT NULL AUTO_INCREMENT,
username
varchar(64) DEFAULT NULL,
password
varchar(64) DEFAULT NULL,
icon
varchar(500) DEFAULT NULL COMMENT ‘头像’,
email
varchar(100) DEFAULT NULL COMMENT ‘邮箱’,
nick_name
varchar(200) DEFAULT NULL COMMENT ‘昵称’,
note
varchar(500) DEFAULT NULL COMMENT ‘备注信息’,
create_time
datetime DEFAULT NULL COMMENT ‘创建时间’,
login_time
datetime DEFAULT NULL COMMENT ‘最后登录时间’,
status
int(1) DEFAULT ‘1’ COMMENT ‘帐号启用状态:0->禁用;1->启用’,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COMMENT=‘后台用户表’;
添加数据:
INSERT INTO luo_admin
VALUES (3, ‘admin’, ‘$2a
10
10
10TyteFqX1w78EDX81d4wr6.pJEYm9b9BdjJ1QF/U./ZEz9RuubWOE2’, ‘http://macro-oss.oss-cn-shenzhen.aliyuncs.com/mall/images/20190129/170157_yIl3_1767531.jpg’, ‘admin@163.com’, ‘系统管理员’, ‘系统管理员’, ‘2018-10-08 13:32:47’, ‘2022-06-21 10:35:39’, 1);
INSERT INTO luo_admin
VALUES (10, ‘Test’, ‘$2a
10
10
10amtbA4JqnEshTdTKBAYPzumwDu4PMlbnulYPjGncfra2wXs92ao2K’, NULL, ‘testqq.com’, ‘测试账号’, ‘111’, ‘2022-06-21 10:30:21’, ‘2022-06-21 10:32:50’, 1);
DROP TABLE IF EXISTS luo_admin_permission_relation
;
CREATE TABLE luo_admin_permission_relation
(
id
bigint(20) NOT NULL AUTO_INCREMENT,
admin_id
bigint(20) DEFAULT NULL,
permission_id
bigint(20) DEFAULT NULL,
type
int(1) DEFAULT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COMMENT=‘后台用户和权限关系表(除角色中定义的权限以外的加减权限)’;
INSERT INTO luo_admin_permission_relation
VALUES (1, 3, 16, -1);
INSERT INTO luo_admin_permission_relation
VALUES (2, 3, 17, -1);
INSERT INTO luo_admin_permission_relation
VALUES (3, 3, 18, -1);
INSERT INTO luo_admin_permission_relation
VALUES (4, 3, 19, -1);
INSERT INTO luo_admin_permission_relation
VALUES (5, 3, 20, -1);
INSERT INTO luo_admin_permission_relation
VALUES (6, 3, 21, -1);
CREATE TABLE luo_admin_role_relation
(
id
bigint(20) NOT NULL AUTO_INCREMENT,
admin_id
bigint(20) DEFAULT NULL,
role_id
bigint(20) DEFAULT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8 COMMENT=‘后台用户和角色关系表’;
INSERT INTO luo_admin_role_relation
VALUES (13, 3, 1);
INSERT INTO luo_admin_role_relation
VALUES (21, 3, 5);
INSERT INTO luo_admin_role_relation
VALUES (22, 10, 5);
CREATE TABLE luo_permission
(
id
bigint(20) NOT NULL AUTO_INCREMENT,
pid
bigint(20) DEFAULT NULL COMMENT ‘父级权限id’,
name
varchar(100) DEFAULT NULL COMMENT ‘名称’,
value
varchar(200) DEFAULT NULL COMMENT ‘权限值’,
component
varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT ‘组件名’,
type
int(1) DEFAULT NULL COMMENT ‘权限类型:0->目录;1->菜单;2->按钮(接口绑定权限)’,
uri
varchar(200) DEFAULT NULL COMMENT ‘前端资源路径’,
title
varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT ‘标题’,
create_time
datetime DEFAULT NULL COMMENT ‘创建时间’,
status
int(1) DEFAULT NULL COMMENT ‘启用状态;0->禁用;1->启用’,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8 COMMENT=‘后台用户权限表’;
INSERT INTO luo_permission
VALUES (1, 0, ‘Layout’, ‘pms:medicine:redad’, ‘Layout’, NULL, ‘/’, ‘Layout’, ‘2022-06-15 21:11:28’, 1);
INSERT INTO luo_permission
VALUES (2, 1, ‘Home’, ‘pms:medicine:redad’, ‘Home’, NULL, ‘/home’, ‘首页’, ‘2022-06-15 21:11:31’, 1);
INSERT INTO luo_permission
VALUES (3, 1, ‘BaseCompany’, ‘pms:medicine:redad’, ‘CompanyManage’, NULL, ‘/base/company’, ‘医药公司管理’, ‘2022-06-15 21:16:08’, 1);
INSERT INTO luo_permission
VALUES (4, 1, ‘BaseSale’, ‘pms:medicine:redad’, ‘SaleManage’, NULL, ‘/base/sale’, ‘销售地点管理’, ‘2022-06-15 21:16:11’, 1);
INSERT INTO luo_permission
VALUES (5, 1, ‘BaseCity’, ‘pms:medicine:redad’, ‘CityManage’, NULL, ‘/base/city’, ‘城市信息管理’, ‘2022-06-15 21:16:15’, 1);
INSERT INTO luo_permission
VALUES (6, 1, ‘ManageDrug’, ‘pms:medicine:redad’, ‘DrugManage’, NULL, ‘/manage/drug’, ‘药品信息管理’, ‘2022-06-15 21:16:18’, 1);
INSERT INTO luo_permission
VALUES (7, 1, ‘MedicalPolicy’, ‘pms:medicine:redad’, ‘MedicalPolicy’, NULL, ‘/manage/medical/policy’, ‘医保政策管理’, ‘2022-06-15 21:16:21’, 1);
INSERT INTO luo_permission
VALUES (8, 1, ‘CompanyPolicy’, ‘pms:medicine:redad’, ‘CompanyPolicy’, NULL, ‘/manage/company/policy’, ‘医药公司政策管理’, ‘2022-06-15 21:16:24’, 1);
INSERT INTO luo_permission
VALUES (9, 1, ‘DoctorManage’, ‘pms:medicine:redad’, ‘DoctorManage’, NULL, ‘/manage/doctor’, ‘医生信息管理’, ‘2022-06-15 21:16:28’, 1);
INSERT INTO luo_permission
VALUES (10, 1, ‘MaterialManage’, ‘pms:medicine:redad’, ‘MaterialManage’, NULL, ‘/manage/material’, ‘必备材料管理’, ‘2022-06-15 21:16:32’, 1);
INSERT INTO luo_permission
VALUES (12, 1, ‘luo’, ‘pms:medicine:redad’, ‘Layout’, NULL, ‘/admin’, ‘权限’, ‘2022-06-16 11:40:08’, 1);
INSERT INTO luo_permission
VALUES (13, 12, ‘admin’, ‘pms:medicine:redad’, ‘admin’, NULL, ‘/luo/admin’, ‘用户列表’, ‘2022-06-16 17:16:44’, 1);
INSERT INTO luo_permission
VALUES (14, 12, ‘role’, ‘pms:medicine:redad’, ‘role’, NULL, ‘/luo/role’, ‘角色列表’, ‘2022-06-16 17:18:44’, 1);
INSERT INTO luo_permission
VALUES (15, 12, ‘allocMenu’, ‘pms:medicine:redad’, ‘allocMenu’, NULL, ‘/luo/allocMenu’, ‘菜单列表’, ‘2022-06-16 17:20:47’, 1);
INSERT INTO luo_permission
VALUES (16, 0, ‘adminList’, ‘pms:medicine:redad’, ‘adminList’, NULL, ‘/api/admin/list’, ‘获取用户列表’, ‘2022-06-19 23:06:25’, 1);
INSERT INTO luo_permission
VALUES (17, 0, ‘adminRole’, ‘pms:medicine:redad’, ‘adminRole’, NULL, ‘/api/role/listAll’, ‘获取角色列表’, ‘2022-06-19 23:23:11’, 1);
INSERT INTO luo_permission
VALUES (18, 0, ‘adminRoleUpdate’, ‘pms:medicine:add’, ‘adminRoleUpdate’, NULL, ‘/api/admin/role/update’, ‘修改用户角色’, ‘2022-06-20 11:02:53’, 1);
INSERT INTO luo_permission
VALUES (19, 0, ‘updateAdmin’, ‘pms:medicine:update’, ‘updateAdmin’, NULL, ‘/api/admin/update/v/’, ‘修改用户数据’, ‘2022-06-20 11:21:51’, 1);
INSERT INTO luo_permission
VALUES (20, 0, ‘deleteAdmin’, ‘pms:medicine:delete’, ‘deleteAdmin’, NULL, ‘/api/admin/delete/v/’, ‘删除用户数据’, ‘2022-06-21 09:47:31’, 1);
INSERT INTO luo_permission
VALUES (21, 0, ‘addAdmin’, ‘pms:medicine:add’, ‘addAdmin’, NULL, ‘/api/admin/register’, ‘添加用户数据’, ‘2022-06-21 10:13:45’, 1);
DROP TABLE IF EXISTS luo_role
;
CREATE TABLE luo_role
(
id
bigint(20) NOT NULL AUTO_INCREMENT,
name
varchar(100) DEFAULT NULL COMMENT ‘名称’,
description
varchar(500) DEFAULT NULL COMMENT ‘描述’,
admin_count
int(11) DEFAULT NULL COMMENT ‘后台用户数量’,
create_time
datetime DEFAULT NULL COMMENT ‘创建时间’,
status
int(1) DEFAULT ‘1’ COMMENT ‘启用状态:0->禁用;1->启用’,
sort
int(11) DEFAULT ‘0’,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT=‘后台用户角色表’;
INSERT INTO luo_role
VALUES (1, ‘系统管理员’, ‘系统管理员’, 0, ‘2018-09-30 15:46:11’, 1, 0);
INSERT INTO luo_role
VALUES (5, ‘医生’, ‘医生’, 0, ‘2022-06-20 10:50:48’, 1, 0);
DROP TABLE IF EXISTS luo_role_permission_relation
;
CREATE TABLE luo_role_permission_relation
(
id
bigint(20) NOT NULL AUTO_INCREMENT,
role_id
bigint(20) DEFAULT NULL,
permission_id
bigint(20) DEFAULT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8 COMMENT=‘后台用户角色和权限关系表’;
INSERT INTO luo_role_permission_relation
VALUES (1, 1, 1);
INSERT INTO luo_role_permission_relation
VALUES (2, 1, 2);
INSERT INTO luo_role_permission_relation
VALUES (3, 1, 3);
INSERT INTO luo_role_permission_relation
VALUES (4, 1, 4);
INSERT INTO luo_role_permission_relation
VALUES (5, 1, 5);
INSERT INTO luo_role_permission_relation
VALUES (6, 1, 6);
INSERT INTO luo_role_permission_relation
VALUES (7, 1, 7);
INSERT INTO luo_role_permission_relation
VALUES (8, 1, 8);
INSERT INTO luo_role_permission_relation
VALUES (18, 1, 9);
INSERT INTO luo_role_permission_relation
VALUES (19, 1, 10);
INSERT INTO luo_role_permission_relation
VALUES (21, 1, 11);
INSERT INTO luo_role_permission_relation
VALUES (22, 1, 12);
INSERT INTO luo_role_permission_relation
VALUES (23, 1, 13);
INSERT INTO luo_role_permission_relation
VALUES (24, 1, 14);
INSERT INTO luo_role_permission_relation
VALUES (25, 1, 15);
INSERT INTO luo_role_permission_relation
VALUES (28, 5, 1);
INSERT INTO luo_role_permission_relation
VALUES (29, 5, 2);
二、创建SpringSecurity配置类
1.白名单配置
@Component
@ConfigurationProperties(prefix = "secure.ignored")
public class IgnoreUrlsConfig {
private List<String> urls = new ArrayList<>();
public List<String> getUrls() {
return urls;
}
public void setUrls(List<String> urls) {
this.urls = urls;
}
}
secure:
ignored:
urls: #安全路径白名单
- /**/*.html
- /**/*.js
- /**/*.css
- /**/*.png
- /favicon.ico
- /actuator/**
- /login
- /permissions
import com.lm.common.utils.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Configuration
public class SecurityConfig {
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Autowired
private DynamicSecurityFilter dynamicSecurityFilter;
@Bean
SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry =
httpSecurity.authorizeRequests();
//添加白名单,设置不需要拦截的内容
for (String url : ignoreUrlsConfig.getUrls()) {
registry.antMatchers(url).permitAll();
}
//允许跨域请求的OPTIONS请求
registry.antMatchers(HttpMethod.OPTIONS,"/**")
.permitAll();
httpSecurity.csrf()// 由于使用的是JWT,我们这里不需要csrf
.disable()
.cors().configurationSource(corsConfigurationSource()).and()
.sessionManagement()// 基于token,所以不需要session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest()// 除上面外的所有请求全部需要鉴权认证
.authenticated();
// 禁用缓存
httpSecurity.headers().cacheControl();
//配置JWT过滤器
httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
//添加自定义未授权和未登录结果返回
httpSecurity.exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler)
.authenticationEntryPoint(restAuthenticationEntryPoint);
//有动态权限配置时添加动态权限校验过滤器,当遇到被拦截的请求时会进入到这里验证
registry.and().addFilterBefore(dynamicSecurityFilter, FilterSecurityInterceptor.class);
//登录成功处理器
return httpSecurity.build();
}
/**
* 处理跨域问题
* @return
*/
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
configuration.addAllowedHeader("*");
configuration.setAllowedMethods(Collections.singletonList("*"));
configuration.setAllowedOriginPatterns(Collections.singletonList("*"));
configuration.setMaxAge(Duration.ofHours(1));
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
package com.lm.common.utils;
import cn.hutool.core.collection.CollUtil;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Iterator;
/**
* @author luoming
* 动态权限决策管理器,用于判断用户是否有访问权限
* @date 2022/6/12 11:32
*/
@Component
public class DynamicAccessDecisionManager implements AccessDecisionManager {
/**
*
* @param authentication 当前正在请求受包含对象的Authentication
* @param object 受保护对象,其可以是一个MethodInvocation、JoinPoint或FilterInvocation。
* @param configAttributes 与正在请求的受保护对象相关联的配置属性
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
// 当接口未被配置资源时直接放行
if (CollUtil.isEmpty(configAttributes)){
return;
}
//将访问所需资源和用户拥有资源进行比较
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while (iterator.hasNext()){
ConfigAttribute configAttribute = iterator.next();
String needAuthority = configAttribute.getAttribute();
for (GrantedAuthority authority : authentication.getAuthorities()) {
if (needAuthority.trim().equals(authority.getAuthority())){
return;
}
}
}
throw new AccessDeniedException("抱歉,您没有访问权限");
}
/**
* 表示当前AccessDecisionManager是否支持对应的ConfigAttribute
* @param attribute
* @return
*/
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
/**
* 表示当前AccessDecisionManager是否支持对应的受保护对象类型
* @param clazz
* @return
*/
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
package com.lm.common.utils;
import com.lm.config.IgnoreUrlsConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;
/**
* @author luoming
* 动态权限过滤器,用于实现基于路径的动态权限过滤
* @date 2022/6/8 21:24
*/
@Component
public class DynamicSecurityFilter extends AbstractSecurityInterceptor implements Filter {
@Autowired
private DynamicSecurityMetadataSource dynamicSecurityMetadataSource;
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
//FilterInvocation
FilterInvocation fi = new FilterInvocation(servletRequest,servletResponse,filterChain);
//如果是options请求就放行,
//当发起跨域请求时,由于安全原因,触发一定条件时浏览器会在正式请求之前自动先发起 OPTIONS 请求,即 CORS 预检请求,服务器若接受该跨域请求,浏览器才继续发起正式请求。
if (request.getMethod().equals(HttpMethod.OPTIONS.toString())){
fi.getChain().doFilter(fi.getRequest(),fi.getResponse());
return;
}
//白名单直接放行
PathMatcher pathMatcher = new AntPathMatcher();
List<String> urls = ignoreUrlsConfig.getUrls();
for (String url : urls) {
if (pathMatcher.match(url,request.getRequestURI())){
fi.getChain().doFilter(fi.getRequest(),fi.getResponse());
return;
}
}
//此处会调用AccessDecisionManager中的decide方法进行鉴权操作
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(),fi.getResponse());
}catch (Exception e){
super.afterInvocation(token,null);
}
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return dynamicSecurityMetadataSource;
}
@Autowired
public void setMyAccessDecisionManager(DynamicAccessDecisionManager dynamicAccessDecisionManager){
super.setAccessDecisionManager(dynamicAccessDecisionManager);
}
}
package com.lm.common.utils;
import cn.hutool.core.util.URLUtil;
import com.lm.pojo.UmsAdmin;
import com.lm.pojo.UmsPermission;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.core.Authentication;
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.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import java.util.*;
/**
* @author luoming
* 动态权限数据源,用于获取动态权限规则
* @date 2022/6/8 21:27
*/
@Component
public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
List<ConfigAttribute> configAttributes = new ArrayList<>();
//获取当前访问路径
String url = ((FilterInvocation) object).getRequestUrl();
//http://localhost:8080/search/name=zhangsan&age=20 完事后只保留/search
//该方法用于获取URL链接中的path部分字符串
String path = URLUtil.getPath(url);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal1 = authentication.getPrincipal();
UmsAdmin userDetails = null;
if (principal1 instanceof UserDetails) {
userDetails = (UmsAdmin) principal1;
List<UmsPermission> permissions = userDetails.getPermissions();
PathMatcher pathMatcher = new AntPathMatcher();
for (UmsPermission umsPermission : permissions) {
if (pathMatcher.match(umsPermission.getUri(), path)) {
SecurityConfig securityConfig = new SecurityConfig(umsPermission.getId() + ":" + umsPermission.getValue());
configAttributes.add(securityConfig);
}
}
}
if (configAttributes.isEmpty()){
//没有匹配到资源,返回ROLE_LOGIN
return SecurityConfig.createList("ROLE_LOGIN");
}
//未设置权限返回空集合
return configAttributes;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
package com.lm.common.utils;
import lombok.extern.slf4j.Slf4j;
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.WebAuthenticationDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
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;
/**
* @author luoming
* JWT登录过滤器 确保一次请求只能通过一次当前过滤器
* 当访问后台服务时,jWt过滤器负责拦截请求验证token
* @date 2022/6/8 15:07
*/
@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtils jwtTokenUtil;
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader(this.tokenHeader);
//如果获取的token不为空,并且 头部是相同的就返回true
if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
String userToken = authHeader.substring(this.tokenHead.length());
String userName = jwtTokenUtil.getUserNameFromToken(userToken);
log.info("checking userName:{}",userName);
//判断数据库中是否有此用户,并且判断没有其他用户已登录
if (userName != null && SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userName);
//判断当前用户是否过期
if (jwtTokenUtil.validateToken(userToken,userDetails)){
//验证密码
//用户名和密码被过滤器获取到,封装成Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
log.info("checking userName:{}",userName);
//如果用户存在且用户的密码相同,则在SecurityContextHolder.getContext().setAuthentication()放入authentication
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
}
filterChain.doFilter(request,response);
}
}
package com.lm.common.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author luoming
* jwt 生成工具
* jwt token格式: header.payload.signature
* header格式(算法、token的类型)
* {"alg":"HS521":"JWT"}
* payload的格式(用户名、创建时间、生成时间):
* * {"sub":"wang","created":1489079981393,"exp":1489684781}
* * signature的生成算法:
* * HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
* @date 2022/6/7 21:29
*/
@Component
@Slf4j
public class JwtTokenUtils {
@Value("${jwt.expiration}")
private Long expiration;
@Value("${jwt.secret}")
private String secret;
private static final String CLAIM_KEY_USERNAME = "sub";
private static final String CLAIM_KEY_CREATED = "created";
/**
* 生成token
* @param claims
* @return
*/
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 从token中获取JWT中的负载
* @param token
* @return
*/
private Claims getClaimsFromToken(String token){
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
log.info("JWT格式验证失败:{}",token);
}
return claims;
}
public String getUserNameFromToken(String token){
String userName;
try {
Claims claims = getClaimsFromToken(token);
userName = claims.getSubject();
}catch (Exception e){
userName = null;
}
return userName;
}
/**
* 生成token过期时间
* @return
*/
public Date generateExpirationDate(){
return new Date(System.currentTimeMillis()+expiration*1000);
}
/**
* 判断token是否过期
* @param token
* @return
*/
public boolean isTokenExpired(String token){
Date expiredDateFromToken = getExpiredDateFromToken(token);
return expiredDateFromToken.before(new Date());
}
/**
* 根据token获取过期时间
* @param token
* @return
*/
public Date getExpiredDateFromToken(String token){
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
/**
* 验证token是否有效
* @param token
* @param userDetails
* @return
*/
public boolean validateToken(String token, UserDetails userDetails){
String userName = getUserNameFromToken(token);
return userName.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
/**
* 根据用户信息生成token
* @param userDetails
* @return
*/
public String generateToken(UserDetails userDetails){
Map<String,Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME,userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED,new Date());
return generateToken(claims);
}
/**
* 判断是否可以刷新token
* @param token
* @return
*/
public boolean canRefresh(String token){return isTokenExpired(token);}
/**
* 刷新token
*/
public String refreshToken(String token){
Claims claimsFromToken = getClaimsFromToken(token);
claimsFromToken.put(CLAIM_KEY_CREATED,new Date());
return generateToken(claimsFromToken);
}
}
package com.lm.common.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.lm.common.api.CommonResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 当未登录或者token失效访问接口时,自定义的返回结果
*/
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
CommonResult<String> unauthorized = CommonResult.unauthorized(authException.getMessage());
response.getWriter().println(JSONObject.toJSONString(unauthorized));
response.getWriter().flush();
}
}
package com.lm.common.utils;
import com.alibaba.fastjson.JSONObject;
import com.lm.common.api.CommonResult;
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.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 当访问接口没有权限时,自定义的返回结果
* Created by macro on 2018/4/26.
*/
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler{
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException e) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().println(JSONObject.toJSONString(CommonResult.forbidden(e.getMessage())));
response.getWriter().flush();
}
}
设置跨域
package com.lm.common.filter;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author luoming
* @date 2022/6/14 23:44
*/
@Component
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@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, PUT, OPTIONS, DELETE, PATCH");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
response.setHeader("Access-Control-Expose-Headers", "Location");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
权限实体:
public class UmsAdmin implements Serializable, UserDetails {
private Long id;
private String username;
private String password;
@ApiModelProperty(value = "头像")
private String icon;
@ApiModelProperty(value = "邮箱")
private String email;
@ApiModelProperty(value = "昵称")
private String nickName;
@ApiModelProperty(value = "备注信息")
private String note;
@ApiModelProperty(value = "创建时间")
private Date createTime;
@ApiModelProperty(value = "最后登录时间")
private Date loginTime;
@ApiModelProperty(value = "帐号启用状态:0->禁用;1->启用")
private Integer status;
private static final long serialVersionUID = 1L;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public String getUsername() {
return username;
}
private List<UmsPermission> permissions;
public List<UmsPermission> getPermissions() {
return permissions;
}
public void setPermissions(List<UmsPermission> permissions) {
this.permissions = permissions;
}
提示账号是否过期 true 末过期 false 过期
@Override
public boolean isAccountNonExpired() {
return true;
}
是否处于锁定状态
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
//指示用户凭证(密码) 是否过期 true 未过期 false过期
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
boolean enabled = true;
if (this.status == 0){
enabled = false;
}
return enabled;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
//返回当前用户的权限
for (UmsPermission umsPermission : permissions) {
authorities.add(new SimpleGrantedAuthority(umsPermission.getId() + ":" + umsPermission.getValue()));
}
return authorities;
}
@Override
public String getPassword() {
return password;
}
mapper 层
@Mapper
public interface AdminMapper {
public UmsAdmin loadAdminByUserName(String userName);
public List<UmsRole> getUmsRole(Long id);
}
@Mapper
public interface UmsAdminRoleRelationMapper {
/**
* 获取用户所有权限(包括+-权限)
*/
List<UmsPermission> getPermissionList(@Param("adminId") Long adminId);
}
service层
public interface AdminService {
/**
* 登录功能
* @param userName 用户名
* @param passWord 密码
* @return 生成的JWT的token
*/
public Map<String, Object> login(String userName,String passWord);
/**
* 获取用户所有权限(包括角色权限和+-权限)
*/
List<UmsPermission> getPermissionList(Long adminId);
@Service
@Slf4j
public class AdminServiceImpl implements AdminService, UserDetailsService {
@Autowired
private AdminMapper adminMapper;
@Autowired
private JwtTokenUtils jwtTokenUtil;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UmsAdminRoleRelationMapper umsAdminRoleRelationMapper;
@Autowired
private UmsRoleMapper umsRoleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UmsAdmin umsAdmin = adminMapper.loadAdminByUserName(username);
if (umsAdmin != null){
List<UmsPermission> permissionList = umsAdminRoleRelationMapper.getPermissionList(umsAdmin.getId());
umsAdmin.setPermissions(permissionList);
return umsAdmin;
}
throw new UsernameNotFoundException("用户名或密码错误");
}
@Override
public Map<String, Object> login(String userName, String passWord) {
Map<String, Object> loginResult = new HashMap<>();
String token = null;
UserDetails userDetails = null;
try {
userDetails = loadUserByUsername(userName);
if(userDetails==null){
return loginResult;
}
if (!passwordEncoder.matches(passWord, userDetails.getPassword())) {
throw new BadCredentialsException("密码不正确");
}
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
token = jwtTokenUtil.generateToken(userDetails);
} catch (AuthenticationException e) {
log.warn("登录异常:{}", e.getMessage());
}
loginResult.put("token",token);
loginResult.put("userInfo",(UmsAdmin)userDetails);
return loginResult;
}
@Override
public List<UmsPermission> getPermissionList(Long adminId) {
return umsAdminRoleRelationMapper.getPermissionList(adminId);
}
}
mapper映射
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lm.mapper.UmsAdminRoleRelationMapper">
<select id="getPermissionList" resultType="com.lm.pojo.UmsPermission">
SELECT
p.*
FROM
ums_admin_role_relation ar
LEFT JOIN ums_role r ON ar.role_id = r.id
LEFT JOIN ums_role_permission_relation rp ON r.id = rp.role_id
LEFT JOIN ums_permission p ON rp.permission_id = p.id
WHERE
ar.admin_id = #{adminId}
AND p.id IS NOT NULL
AND p.id NOT IN (
SELECT
p.id
FROM
ums_admin_permission_relation pr
LEFT JOIN ums_permission p ON pr.permission_id = p.id
WHERE
pr.type = - 1
AND pr.admin_id = #{adminId}
)
UNION
SELECT
p.*
FROM
ums_admin_permission_relation pr
LEFT JOIN ums_permission p ON pr.permission_id = p.id
WHERE
pr.type = 1
AND pr.admin_id = #{adminId}
</select>
</mapper>
控制层:
package com.lm.controller;
import com.lm.common.api.CommonResult;
import com.lm.mapper.UmsAdminRoleRelationMapper;
import com.lm.pojo.UmsPermission;
import com.lm.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author luoming
* @date 2022/6/12 23:07
*/
@RestController
public class LoginController {
@Autowired
private AdminService adminService;
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
@Autowired
private UmsAdminRoleRelationMapper umsAdminRoleRelationMapper;
@RequestMapping(value = "/login",method = RequestMethod.POST)
public CommonResult login(@RequestParam String username, @RequestParam String password) {
Map<String, Object> loginResult = adminService.login(username, password);
if (loginResult.get("token") == null) {
return CommonResult.validateFailed("用户名或密码错误");
}
loginResult.put("tokenHead", tokenHead);
return CommonResult.success(loginResult);
}
@RequestMapping(value = "/permissions",method = RequestMethod.GET)
public CommonResult permissions(@RequestParam Long id){
List<UmsPermission> permissionList = umsAdminRoleRelationMapper.getPermissionList(id);
List<UmsPermission> failPermission = new ArrayList<>();
for (UmsPermission umsPermission : permissionList) {
if (umsPermission.getPid() == 0){
failPermission.add(setChildren(umsPermission, permissionList));
}
}
return CommonResult.success(failPermission);
}
public UmsPermission setChildren(UmsPermission father,List<UmsPermission> permissions){
List<UmsPermission> list = new ArrayList<>();
for (UmsPermission permission : permissions) {
if (father.getId().equals(permission.getPid())){
father.setChildren(list);
father.getChildren().add(setChildren(permission,permissions));
}
}
return father;
}
}