从上至下操作,直接上手SpringSecurity
使用的是Mybatis-Plus做数据库操作
org.springframework.boot
spring-boot-starter-parent
2.2.1.RELEASE
org.springframework.boot
spring-boot-starter-security
com.alibaba
druid
1.2.1
mysql
mysql-connector-java
com.baomidou
mybatis-plus-boot-starter
3.2.0
org.projectlombok
lombok
com.alibaba
fastjson
1.2.15
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
1、用户表(sys_user)
密码是加密后的(123456)
/*
Navicat Premium Data Transfer
Source Server : 本机
Source Server Type : MySQL
Source Server Version : 80029
Source Host : localhost:3306
Source Schema : game
Target Server Type : MySQL
Target Server Version : 80029
File Encoding : 65001
Date: 10/02/2023 17:04:23
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` int NOT NULL AUTO_INCREMENT,
`account` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '账号',
`user_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
`password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户密码',
`last_login_time` datetime NULL DEFAULT NULL COMMENT '上一次登录时间',
`enabled` tinyint(1) NULL DEFAULT 1 COMMENT '账号是否可用。默认为1(可用)',
`not_expired` tinyint(1) NULL DEFAULT 1 COMMENT '是否过期。默认为1(没有过期)',
`account_not_locked` tinyint(1) NULL DEFAULT 1 COMMENT '账号是否锁定。默认为1(没有锁定)',
`credentials_not_expired` tinyint(1) NULL DEFAULT 1 COMMENT '证书(密码)是否过期。默认为1(没有过期)',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
`create_user` int NULL DEFAULT NULL COMMENT '创建人',
`update_user` int NULL DEFAULT NULL COMMENT '修改人',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'user1', '用户1', '$2a$10$47lsFAUlWixWG17Ca3M/r.EPJVIb7Tv26ZaxhzqN65nXVcAhHQM4i', '2019-09-04 20:25:36', 1, 1, 1, 1, '2019-08-29 06:28:36', '2019-09-04 20:25:36', 1, 1);
INSERT INTO `sys_user` VALUES (2, 'user2', '用户2', '$2a$10$uSLAeON6HWrPbPCtyqPRj.hvZfeM.tiVDZm24/gRqm4opVze1cVvC', '2019-09-05 00:07:12', 1, 1, 1, 1, '2019-08-29 06:29:24', '2019-09-05 00:07:12', 1, 2);
SET FOREIGN_KEY_CHECKS = 1;
2、权限表(sys_permission)
/*
Navicat Premium Data Transfer
Source Server : 本机
Source Server Type : MySQL
Source Server Version : 80029
Source Host : localhost:3306
Source Schema : game
Target Server Type : MySQL
Target Server Version : 80029
File Encoding : 65001
Date: 10/02/2023 17:19:56
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
`permission_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '权限code',
`permission_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '权限名',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '权限表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES (1, 'create_user', '创建用户');
INSERT INTO `sys_permission` VALUES (2, 'query_user', '查看用户');
INSERT INTO `sys_permission` VALUES (3, 'delete_user', '删除用户');
INSERT INTO `sys_permission` VALUES (4, 'modify_user', '修改用户');
SET FOREIGN_KEY_CHECKS = 1;
3、角色表(sys_role)
/*
Navicat Premium Data Transfer
Source Server : 本机
Source Server Type : MySQL
Source Server Version : 80029
Source Host : localhost:3306
Source Schema : game
Target Server Type : MySQL
Target Server Version : 80029
File Encoding : 65001
Date: 10/02/2023 17:20:23
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
`role_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色code',
`role_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色名',
`role_description` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色说明',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户角色表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'admin', '管理员', '管理员,拥有所有权限');
INSERT INTO `sys_role` VALUES (2, 'user', '普通用户', '普通用户,拥有部分权限');
SET FOREIGN_KEY_CHECKS = 1;
4、角色权限关系表(sys_role_permission_relation)
/*
Navicat Premium Data Transfer
Source Server : 本机
Source Server Type : MySQL
Source Server Version : 80029
Source Host : localhost:3306
Source Schema : game
Target Server Type : MySQL
Target Server Version : 80029
File Encoding : 65001
Date: 10/02/2023 17:21:29
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_role_permission_relation
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission_relation`;
CREATE TABLE `sys_role_permission_relation` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
`role_id` int NULL DEFAULT NULL COMMENT '角色id',
`permission_id` int NULL DEFAULT NULL COMMENT '权限id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色-权限关联关系表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role_permission_relation
-- ----------------------------
INSERT INTO `sys_role_permission_relation` VALUES (1, 1, 1);
INSERT INTO `sys_role_permission_relation` VALUES (2, 1, 2);
INSERT INTO `sys_role_permission_relation` VALUES (3, 1, 3);
INSERT INTO `sys_role_permission_relation` VALUES (4, 1, 4);
SET FOREIGN_KEY_CHECKS = 1;
5、用户角色关系表(sys_user_role_relation)
/*
Navicat Premium Data Transfer
Source Server : 本机
Source Server Type : MySQL
Source Server Version : 80029
Source Host : localhost:3306
Source Schema : game
Target Server Type : MySQL
Target Server Version : 80029
File Encoding : 65001
Date: 10/02/2023 17:22:13
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_user_role_relation
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role_relation`;
CREATE TABLE `sys_user_role_relation` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
`user_id` int NULL DEFAULT NULL COMMENT '用户id',
`role_id` int NULL DEFAULT NULL COMMENT '角色id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户角色关联关系表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user_role_relation
-- ----------------------------
INSERT INTO `sys_user_role_relation` VALUES (1, 1, 1);
INSERT INTO `sys_user_role_relation` VALUES (2, 2, 2);
SET FOREIGN_KEY_CHECKS = 1;
6、请求路径表(sys_request_path)
/*
Navicat Premium Data Transfer
Source Server : 本机
Source Server Type : MySQL
Source Server Version : 80029
Source Host : localhost:3306
Source Schema : game
Target Server Type : MySQL
Target Server Version : 80029
File Encoding : 65001
Date: 10/02/2023 17:23:23
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_request_path
-- ----------------------------
DROP TABLE IF EXISTS `sys_request_path`;
CREATE TABLE `sys_request_path` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
`url` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '请求路径',
`description` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '路径描述',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '请求路径' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_request_path
-- ----------------------------
INSERT INTO `sys_request_path` VALUES (1, '/demo/getUser', '查询用户');
SET FOREIGN_KEY_CHECKS = 1;
7、请求路径权限关系表(sys_request_path_permission_relation)
/*
Navicat Premium Data Transfer
Source Server : 本机
Source Server Type : MySQL
Source Server Version : 80029
Source Host : localhost:3306
Source Schema : game
Target Server Type : MySQL
Target Server Version : 80029
File Encoding : 65001
Date: 10/02/2023 17:23:29
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_request_path_permission_relation
-- ----------------------------
DROP TABLE IF EXISTS `sys_request_path_permission_relation`;
CREATE TABLE `sys_request_path_permission_relation` (
`id` int NULL DEFAULT NULL COMMENT '主键id',
`url_id` int NULL DEFAULT NULL COMMENT '请求路径id',
`permission_id` int NULL DEFAULT NULL COMMENT '权限id'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '路径权限关联表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_request_path_permission_relation
-- ----------------------------
INSERT INTO `sys_request_path_permission_relation` VALUES (1, 1, 2);
SET FOREIGN_KEY_CHECKS = 1;
说明:
角色1对应的是管理员角色,有全部权限(这里主要演示的是query_user权限)
角色2对应的是用户,没有任何权限
实现从数据库中获取用户的信息,自定义登录逻辑
@Service
public class UserService implements UserDetailsService{
@Autowired
private UserMapper userMapper;
@Autowired
private UserRoleMapper userRoleMapper;
@Autowired
private RolePermissionMapper rolePermissionMapper;
@Autowired
private PermissionMapper permissionMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//需要构造出 org.springframework.security.core.userdetails.User 对象并返回
/**
* String username:用户名
* String password: 密码
* boolean enabled: 账号是否可用
* boolean accountNonExpired:账号是否没有过期
* boolean credentialsNonExpired:密码是否没有过期
* boolean accountNonLocked:账号是否没有被锁定
* Collection authorities):用户权限列表
*/
//1、根据用户名查询用户信息
QueryWrapper queryWrapper=new QueryWrapper<>();
queryWrapper.eq("account",username);
UserEntity userEntity = userMapper.selectOne(queryWrapper);
//2、根据用户名查询用户的权限信息
//查询userId
Integer id = userEntity.getId();
//查询权限id
QueryWrapper queryWrapper1=new QueryWrapper<>();
queryWrapper1.eq("user_id", id);
UserRoleRelation userRoleRelation = userRoleMapper.selectOne(queryWrapper1);
Integer roleId = userRoleRelation.getRoleId();
//根据角色信息查询对应的权限信息
//查询权限id
QueryWrapper rolePermissionRelationQueryWrapper = new QueryWrapper<>();
rolePermissionRelationQueryWrapper.eq("role_id", roleId);
List rolePermissionRelations = rolePermissionMapper.selectList(rolePermissionRelationQueryWrapper);
//权限列表
List grantedAuthorities = new ArrayList<>();
for (RolePermissionRelation rolePermissionRelation : rolePermissionRelations) {
PermissionEntity permissionEntity = permissionMapper.selectById(rolePermissionRelation.getPermissionId());
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permissionEntity.getPermissionCode());
grantedAuthorities.add(grantedAuthority);
}
//3、构造出我们需要的org.springframework.security.core.userdetails.User对象
User user = new User(userEntity.getUserName(), userEntity.getPassword(), userEntity.getEnabled() == 1 ? true : false, userEntity.getNotExpired() == 1 ? true : false, userEntity.getCredentialsNotExpired() == 1 ? true : false, userEntity.getAccountNotLocked() == 1 ? true : false, grantedAuthorities);
return user;
}
进行配置使用我们自定义的UserService,并设置密码加密
/**
* @ClassName WebSecurityConfig
* @Author ylh
* @Date 2023/2/7 21:28
* @Description
*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService() {
//获取用户账号密码及权限信息
return new UserService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//获取用户账号密码及权限信息
auth.userDetailsService(userDetailsService());
}
// 设置默认的加密方式(强hash方式加密),注入就行
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
这里是使用表单进行登录,使用JSON进行登录在下面有介绍
默认的登录接口的/login,下面我们使用自定义的路径
在WebSecurityConfig类中重写configure方法(注意参数是HttpSecurity)
@Override
protected void configure(HttpSecurity http)
测试:
使用postman测试http://localhost:8080/user/login?username=user1&password=123456
注意:要使用post请求
创建两个Component
/**
* @Author: ylh
* @Description: 登录成功处理逻辑
*/
@Component
public class CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, org.springframework.security.core.Authentication authentication) throws IOException, ServletException {
//此处还可以进行一些处理,比如登录成功之后可能需要返回给前台当前用户有哪些菜单权限,
//进而前台动态的控制菜单的显示等,具体根据自己的业务需求进行扩展
//返回json数据
JsonResult result = ResultTool.success();
//处理编码方式,防止中文乱码的情况
httpServletResponse.setContentType("text/json;charset=utf-8");
//塞到HttpServletResponse中返回给前台
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}
/**
* @Author:
* @Description: 登录失败处理逻辑
*/
@Component
public class CustomizeAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException, IOException {
//返回json数据
JsonResult result = null;
if (e instanceof AccountExpiredException) {
//账号过期
result = ResultTool.fail(ResultCode.USER_ACCOUNT_EXPIRED);
} else if (e instanceof BadCredentialsException) {
//密码错误
result = ResultTool.fail(ResultCode.USER_CREDENTIALS_ERROR);
} else if (e instanceof CredentialsExpiredException) {
//密码过期
result = ResultTool.fail(ResultCode.USER_CREDENTIALS_EXPIRED);
} else if (e instanceof DisabledException) {
//账号不可用
result = ResultTool.fail(ResultCode.USER_ACCOUNT_DISABLE);
} else if (e instanceof LockedException) {
//账号锁定
result = ResultTool.fail(ResultCode.USER_ACCOUNT_LOCKED);
} else if (e instanceof InternalAuthenticationServiceException) {
//用户不存在
result = ResultTool.fail(ResultCode.USER_ACCOUNT_NOT_EXIST);
}else{
//其他错误
result = ResultTool.fail(ResultCode.COMMON_FAIL);
}
//处理编码方式,防止中文乱码的情况
httpServletResponse.setContentType("text/json;charset=utf-8");
//塞到HttpServletResponse中返回给前台
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}
在配置类中进行配置
先注入进来
@Autowired
private CustomizeAuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private CustomizeAuthenticationFailureHandler authenticationFailureHandler;
设置
替换原来的拦截器,使用我们自定义的
创建一个接口
@GetMapping("/getUser")
public String getUser(){
return "查询用户";
}
自定义component
/**
* @Author: ylh
* @Description: 未登录的异常处理
*/
@Component
public class CustomizeAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
//自定义返回信息返回
JsonResult result = ResultTool.fail(ResultCode.USER_NOT_LOGIN);
httpServletResponse.setContentType("text/json;charset=utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}
注入到配置类中
@Autowired
private CustomizeAuthenticationEntryPoint authenticationEntryPoint;
进行相关 配置
设置权限,如果不设置,不用登录也可以访问(因为被我们重写了void configure(HttpSecurity http)这个方法)
http.authorizeRequests().antMatchers("/demo/getUser").hasAuthority("query_user")//为getUser接口设置权限
.and()
//异常处理(权限拒绝、登录失效等)
//匿名用户访问无权限资源时的异常处理
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).
springsecurity默认退出登录的url地址为http://localhost:8080/logout
创建Component
/**
* @Author: ylh
* @Description: 登出成功处理逻辑
*/
@Component
public class CustomizeLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException, IOException {
JsonResult result = ResultTool.success();
httpServletResponse.setContentType("text/json;charset=utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}
在配置类中
将CustomizeLogoutSuccessHandler注入进来
设置
//登出
and().logout().
permitAll().//允许所有用户
logoutSuccessHandler(logoutSuccessHandler).//登出成功处理逻辑
deleteCookies("JSESSIONID").//登出之后删除cookie
继承UsernamePasswordAuthenticationFilter,重写attemptAuthentication方法
/**
* @ClassName CustomAuthenticationFilter
* @Author ylh
* @Date 2023/2/9 21:39
* @Description
*/
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
|| request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
ObjectMapper mapper = new ObjectMapper();
UsernamePasswordAuthenticationToken authRequest = null;
try (InputStream is = request.getInputStream()) {
Map authenticationBean = mapper.readValue(is, Map.class);
authRequest = new UsernamePasswordAuthenticationToken(
authenticationBean.get("username"), authenticationBean.get("password"));
} catch (IOException e) {
e.printStackTrace();
authRequest = new UsernamePasswordAuthenticationToken(
"", "");
} finally {
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
else {
return super.attemptAuthentication(request, response);
}
}
}
配置类中,创建一个Bean
@Bean
CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
//登录成功的返回
JsonResult result = ResultTool.success();
out.write(new ObjectMapper().writeValueAsString(result));
out.flush();
out.close();
}
});
filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
//设置登录失败的返回结果
JsonResult result = ResultTool.fail();
out.write(new ObjectMapper().writeValueAsString(result));
out.flush();
out.close();
}
});
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
在configure(HttpSecurity http)方法中,替换原有的过滤器,使用我们自定义的过滤器
http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
使用JSON进行登录配置类中就不需要formLogin(),退出登录的方法是一样的
目前configure的内容
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/demo/getUser").hasAuthority("query_user").//为getUser接口设置权限
and()
// //异常处理(权限拒绝、登录失效等)页面不能进行重定向到登录页面了,
// //匿名用户访问无权限资源时的异常处理
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
//
//登出
.and().logout().
permitAll().//允许所有用户
logoutSuccessHandler(logoutSuccessHandler).//登出成功处理逻辑
deleteCookies("JSESSIONID")//登出之后删除cookie
.and()
.csrf().disable();
http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
前面是直接在配置类中添加的权限控制
http.authorizeRequests()
.antMatchers("/demo/getUser").hasAuthority("query_user").//为
现在是基于数据库实现,将配置类中的权限配置删除
1、编写权限拦截器
/**
* @Author: ylh
* @Description: 权限拦截器
*/
@Service
public class CustomizeAbstractSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
public void setMyAccessDecisionManager(CustomizeAccessDecisionManager accessDecisionManager) {
super.setAccessDecisionManager(accessDecisionManager);
}
@Override
public Class getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
//fi里面有一个被拦截的url
//里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
//再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
}
2、安全元数据源FilterInvocationSecurityMetadataSource
package com.example.demo.service;
import com.example.demo.entity.PermissionEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
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 java.util.Collection;
import java.util.List;
/**
* @Author: Hutengfei
* @Description:
* @Date Create in 2019/9/3 21:06
*/
@Component
public class CustomizeFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
// AntPathMatcher antPathMatcher = new AntPathMatcher();
@Autowired
SysPermissionService sysPermissionService;
@Override
public Collection getAttributes(Object o) throws IllegalArgumentException {
//获取请求地址
String requestUrl = ((FilterInvocation) o).getRequestUrl();
//查询具体某个接口的权限
List permissionList = sysPermissionService.selectListByPath(requestUrl);
if(permissionList == null || permissionList.size() == 0){
//请求路径没有配置权限,表明该请求接口可以任意访问
return null;
}
//请求路径配置了权限
String[] attributes = new String[permissionList.size()];
for(int i = 0;i getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class aClass) {
return true;
}
}
这里是sysPermissionService是查询路径所需要的权限,这里贴出来供大家参考
/**
* @ClassName SysPermissionService
* @Author ylh
* @Date 2023/2/10 15:05
* @Description
*/
@Service
public class SysPermissionService {
@Autowired
private RequestUrlMapper requestUrlMapper;
@Autowired
private RequestPermissionMapper requestPermissionMapper;
@Autowired
private PermissionMapper permissionMapper;
public List<PermissionEntity> selectListByPath(String requestUrl){
//根据URL查询对应权限
List<PermissionEntity> list=new ArrayList<>();
QueryWrapper<RequestUrlEntity> queryWrapper=new QueryWrapper();
queryWrapper.eq("url",requestUrl);
RequestUrlEntity requestUrlEntity = requestUrlMapper.selectOne(queryWrapper);
if (requestUrlEntity!=null){
//查询权限id
QueryWrapper<RequestPermissionEntity> requestPermissionEntityQueryWrapper=new QueryWrapper();
requestPermissionEntityQueryWrapper.eq("url_id",requestUrlEntity.getId());
List<RequestPermissionEntity> requestPermissionEntities = requestPermissionMapper.selectList(requestPermissionEntityQueryWrapper);
//查询权限具体信息
for (RequestPermissionEntity requestPermissionEntity : requestPermissionEntities) {
PermissionEntity permissionEntity = permissionMapper.selectById(requestPermissionEntity.getPermissionId());
list.add(permissionEntity);
}
}
return list;
}
}
3、访问决策管理器AccessDecisionManager
package com.example.demo.service;
import com.example.demo.common.PermissionException;
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: ylh
* @Description: 访问决策管理器
* @Date Create in 2019/9/3 20:38
*/
@Component
public class CustomizeAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object o, Collection collection) throws AccessDeniedException, InsufficientAuthenticationException {
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
ConfigAttribute ca = iterator.next();
//当前请求需要的权限
String needRole = ca.getAttribute();
//当前用户所具有的权限
Collection authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
//权限不足抛出异常,会被AccessDeniedHandler拦截
throw new AccessDeniedException("权限不足!");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class aClass) {
return true;
}
}
4、在WebSecurityConfig中声明
http.authorizeRequests().
withObjectPostProcessor(new ObjectPostProcessor() {
@Override
public O postProcess(O o) {
o.setAccessDecisionManager(accessDecisionManager);//决策管理器
o.setSecurityMetadataSource(securityMetadataSource);//安全元数据源
return o;
}
}).
5、重写AccessDeniedHandler拦截器,变成我们自定义返回的内容
package com.example.demo.config;
import com.alibaba.fastjson.JSON;
import com.example.demo.common.JsonResult;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Service;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Author: ylh
*/
@Service
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException, IOException {
response.setContentType("application/json;charset=UTF-8");
JsonResult jsonResult=new JsonResult<>();
jsonResult.setErrorCode(403);
jsonResult.setErrorMsg("权限不足");
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_OK);
String s = JSON.toJSONString(jsonResult);
response.getWriter().write(s);
}
}
前面是将登录过后的信息存储在cookie中
下面将演示,结合JWT实现token的登录
引入两个依赖
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.17</version>
</dependency>
创建一个工具类
package com.example.demo.utils;
import io.jsonwebtoken.*;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @ClassName JwtUtils
* @Author ylh
* @Date 2023/2/20 9:14
* @Description
*/
@Data
@Component
@ConfigurationProperties(prefix = "ylh.jwt")
public class JwtUtils {
private long expire;
private String secret;
private String header;
// 生成JWT
public String generateToken(String username) {
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + 1000 * expire);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(username)
.setIssuedAt(nowDate)
.setExpiration(expireDate) // 7天过期
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
// 解析JWT
public Claims getClaimsByToken(String jwt) {
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(jwt)
.getBody();
} catch (Exception e) {
return null;
}
}
// 判断JWT是否过期
public boolean isTokenExpired(Claims claims) {
return claims.getExpiration().before(new Date());
}
}
在配置文件中进行相关配置
ylh:
jwt:
header: Authorization #设置请求头名称
expire: 604800 # 7天,s为单位
secret: aiit
流程:当用户第一次登录成功后,返回一个token给前端,每次访问的时候,都要将token放到请求头中
1、修改登录成功的逻辑
在配置类的修改登录成功这一部分的逻辑,添加token的生成
注入jwt工具类
@Autowired
private JwtUtils jwtUtils;
@Bean
CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
//登录成功的返回
JsonResult result = ResultTool.success();
//生成token,返回给前端
String jwt = jwtUtils.generateToken(authentication.getName());
result.setData(jwt);
out.write(new ObjectMapper().writeValueAsString(result));
out.flush();
out.close();
}
});
关闭cookie
//关闭cookie
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
2、添加未登录的异常捕获
package com.example.demo.handler;
import com.alibaba.fastjson.JSON;
import com.example.demo.common.JsonResult;
import com.example.demo.common.ResultCode;
import com.example.demo.common.ResultTool;
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;
/**
* @ClassName JwtAuthenticationEntryPoint
* @Author ylh
* @Date 2023/2/21 9:41
* @Description 请求需要权限的接口时,判断登录状态
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
//自定义返回信息返回
JsonResult result = ResultTool.fail(ResultCode.USER_NOT_LOGIN);
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(JSON.toJSONString(result));
}
}
在配置类中进行配置
//匿名用户访问无权限资源时的异常处理
and()
.exceptionHandling()
//AuthenticationException 是在用户认证的时候出现错误时抛出的异常。主要的子类如图:
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
3、在判断用户名密码之前添加一个验证token的过滤器
JwtAuthenticationFilter
/**
* @ClassName JwtAuthenticationFilter
* @Author ylh
* @Date 2023/2/20 10:34
* @Description
*/
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
@Autowired
JwtUtils jwtUtils;
@Autowired
private UserService userService;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
/**
* 解析我们的jwt,根据解析的username查询数据库,进行权限配置
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String jwt = request.getHeader(jwtUtils.getHeader());
// 这里如果没有jwt,继续往后走,因为后面还有鉴权管理器等去判断是否拥有身份凭证,所以是可以放行的
// 没有jwt相当于匿名访问(比如我们的请求验证码接口),若有一些接口是需要权限的,则不能访问这些接口,在刚才的JwtAuthenticationEntryPoint 配置了
if (StrUtil.isBlankOrUndefined(jwt)) {
chain.doFilter(request, response);
return;
}
Claims claim = jwtUtils.getClaimsByToken(jwt);
if (claim == null) {
throw new JwtException("token 异常");
}
if (jwtUtils.isTokenExpired(claim)) {
throw new JwtException("token 已过期");
}
String username = claim.getSubject();
//获取用户的权限等信息
List grantedAuthorities =userService.getUserPermissionByUsername(username);
// 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, grantedAuthorities);
SecurityContextHolder.getContext().setAuthentication(token);
chain.doFilter(request, response);
}
}
注意:这里有个问题!!!全局异常无法捕获filter里的异常,所以我们直接抛出
JwtException是不可以的
解决办法:这里就使用请求转发给controller,返回给前端
if (claim == null) {
//请求转发给controller
request.getRequestDispatcher("/demo/401").forward(request,response);
// throw new JwtException("token 异常");
}
在controlelr中创建一个异常返回接口
@GetMapping("/401")
public JsonResult error(){
JsonResult jsonResult=new JsonResult(false);
jsonResult.setErrorMsg("token异常");
return jsonResult;
}
userService.getUserPermissionByUsername(username);//根据解析出来的用户名查询对应的权限
,构造User对象
public List getUserPermissionByUsername(String username) {
//1、根据用户名查询用户信息
QueryWrapper queryWrapper=new QueryWrapper<>();
queryWrapper.eq("account",username);
UserEntity userEntity = userMapper.selectOne(queryWrapper);
//2、根据用户名查询用户的权限信息
//查询userId
Integer id = userEntity.getId();
//查询权限id
QueryWrapper queryWrapper1=new QueryWrapper<>();
queryWrapper1.eq("user_id", id);
UserRoleRelation userRoleRelation = userRoleMapper.selectOne(queryWrapper1);
Integer roleId = userRoleRelation.getRoleId();
//根据角色信息查询对应的权限信息
//查询权限id
QueryWrapper rolePermissionRelationQueryWrapper = new QueryWrapper<>();
rolePermissionRelationQueryWrapper.eq("role_id", roleId);
List rolePermissionRelations = rolePermissionMapper.selectList(rolePermissionRelationQueryWrapper);
//权限列表
List grantedAuthorities = new ArrayList<>();
for (RolePermissionRelation rolePermissionRelation : rolePermissionRelations) {
PermissionEntity permissionEntity = permissionMapper.selectById(rolePermissionRelation.getPermissionId());
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permissionEntity.getPermissionCode());
grantedAuthorities.add(grantedAuthority);
}
return grantedAuthorities;
}
在配置类中添加
//添加token过滤器
http.addFilter(jwtAuthenticationFilter());
3、实现退出登录
在配置类中
//登出
.and().logout().
permitAll().//允许所有用户
logoutSuccessHandler(logoutSuccessHandler);
创建CustomizeLogoutSuccessHandler,退出登录拦截器
@Component
public class CustomizeLogoutSuccessHandler implements LogoutSuccessHandler {
@Autowired
private JwtUtils jwtUtils;
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException, IOException {
if (authentication != null) {
new SecurityContextLogoutHandler().logout(httpServletRequest, httpServletResponse, authentication);
}
//请求头置空
httpServletResponse.setHeader(jwtUtils.getHeader(), "");
JsonResult result = ResultTool.success();
httpServletResponse.setContentType("text/json;charset=utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}
依赖坐标
com.github.axet
kaptcha
0.0.9
配置类
@Configuration
public class KaptchaConfig {
@Bean
DefaultKaptcha producer() {
Properties properties = new Properties();
properties.put("kaptcha.border", "no");
properties.put("kaptcha.textproducer.font.color", "black");
properties.put("kaptcha.textproducer.char.space", "4");
properties.put("kaptcha.image.height", "40");
properties.put("kaptcha.image.width", "120");
properties.put("kaptcha.textproducer.font.size", "30");
Config config = new Config(properties);
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
编写一个生成验证码的接口
这里将生成的验证码key存储到redis中(我没有演示,自行设置)
返回给前端一个key
当前端登录时,需要账号,密码,验证码的code,验证码的key
/**
* 生成验证码接口
* @return
* @throws IOException
*/
@GetMapping("/captcha")
public JsonResult Captcha() throws IOException {
String key = UUID.randomUUID().toString();//缓存进入redis中
String code = producer.createText();//生成的验证码内容
BufferedImage image = producer.createImage(code);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(image, "jpg", outputStream);
Base64Encoder encoder = new Base64Encoder();
String str = "data:image/jpeg;base64,";
String base64Img = str + encoder.encode(outputStream.toByteArray());
/**
* TODO 缓存验证码到redis中
*/
JsonResult jsonResult=new JsonResult(true);
jsonResult.setData(code);
jsonResult.setData(MapUtil.builder()
.put("userKey", key)
.put("captcherImg", base64Img)
.build());
return jsonResult;
}
返回结果
{
"success": true,
"errorCode": 200,
"errorMsg": "成功",
"data": {
"captcherImg": "data:image/jpeg;base64,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",
"userKey": "33f02ffb-8d90-4b16-9e7f-f38daf7494df"
}
}
验证码是一个base64编码的图片,前端需要进行解码
自定义一个异常,当验证码错误时,抛出这个异常
/**
* @ClassName CaptchaException
* @Author ylh
* @Date 2023/2/20 14:53
* @Description
*/
public class CaptchaException extends AuthenticationException {
public CaptchaException(String msg) {
super(msg);
}
}
在登录的时候,进行验证码校验
修改我们之前编写的CustomAuthenticationFilter
添加验证码校验
package com.example.demo.filter;
import com.example.demo.exception.CaptchaException;
import com.example.demo.handler.CustomizeAuthenticationEntryPoint;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/**
* @ClassName CustomAuthenticationFilter
* @Author ylh
* @Date 2023/2/9 21:39
* @Description 使用JSON格式登录
*/
/**
* @ClassName CustomAuthenticationFilter
* @Author ylh
* @Date 2023/2/9 21:39
* @Description
*/
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Autowired
private CustomizeAuthenticationEntryPoint customizeAuthenticationEntryPoint;
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
|| request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
ObjectMapper mapper = new ObjectMapper();
UsernamePasswordAuthenticationToken authRequest = null;
InputStream is = null ;
try {
is = request.getInputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
Map authenticationBean = null;
try {
authenticationBean = mapper.readValue(is, Map.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
//获取username的值
String code = authenticationBean.get("code");//getUsernameParameter:username
//获取password
String userKey = authenticationBean.get("userKey");//getPasswordParameter:password
//设置值
/**
* TODO 简单模拟一下
*/
if (code.equals(userKey)){
throw new CaptchaException("验证码有误");
}
authRequest = new UsernamePasswordAuthenticationToken(
authenticationBean.get("username"), authenticationBean.get("password"));
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
else {
return super.attemptAuthentication(request, response);
}
}
}
在配置类中
修改我们之前自定义的登录异常,判断是否是验证码异常
//登录失败
filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
JsonResult result = ResultTool.fail();
if (e instanceof CaptchaException) {
result.setErrorMsg("验证码有误");
}
out.write(new ObjectMapper().writeValueAsString(result));
out.flush();
out.close();
}
});
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
这里直接模拟验证码是否正确(当userKey和code一样的时候,报错)
/**
* TODO 简单模拟一下
*/
if (code.equals(userKey)){
throw new CaptchaException("验证码有误");
}
在后期使用的时候,需要根据userKey查询redis中对应的code
进行比对
附上项目源码地址:
https://gitee.com/bai-xiaoyun/spring-security-entry-use-case.git