简言
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于Spring的应用程序的实际标准。
Spring Security是一个框架,致力于为Java应用程序提供身份验证和授权。像所有Spring项目一样,Spring Security的真正强大之处在于可以轻松扩展以满足自定义要求(- - - 来自官网翻译)
认证 (authentication) 和授权 (authorization) 的区别
举例:你要登机,你需要出示你的 passport 和 ticket,passport 是为了证明你张三确实是你张三,这就是 authentication;而机票是为了证明你张三确实买了票可以上飞机,这就是 authorization
源码地址:https://gitee.com/wangwenlongGitHub/conformity.git
1:库表设计
用户表
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户id',
`name` varchar(255) DEFAULT NULL COMMENT '用户名称',
`age` int(3) DEFAULT NULL COMMENT '用户年龄',
`sex` char(5) DEFAULT NULL COMMENT '用户性别',
`phone` varchar(11) DEFAULT NULL COMMENT '用户电话',
`portrait` varchar(255) DEFAULT NULL COMMENT '用户头像',
`telephone` varchar(16) DEFAULT NULL COMMENT '座机',
`address` varchar(64) DEFAULT NULL COMMENT '地址',
`enabled` tinyint(1) DEFAULT NULL COMMENT '是否启用',
`username` varchar(255) DEFAULT NULL COMMENT '用户',
`password` varchar(255) DEFAULT NULL COMMENT '密码',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
角色表
CREATE TABLE `sys_role` (
`id` bigint(20) NOT NULL COMMENT '编号',
`office_id` bigint(20) DEFAULT NULL COMMENT '归属机构',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '角色名称',
`enname` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '英文名称',
`role_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '角色类型',
`data_scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '数据范围',
`is_sys` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '是否系统数据',
`useable` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '是否可用',
`create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '创建者',
`create_date` datetime NOT NULL COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '更新者',
`update_date` datetime NOT NULL COMMENT '更新时间',
`remarks` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '备注信息',
`del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '0' COMMENT '删除标记',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
权限表
CREATE TABLE `sys_menu` (
`id` bigint(20) NOT NULL COMMENT '编号',
`parent_id` bigint(20) NOT NULL COMMENT '父级编号',
`parent_ids` varchar(2000) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '所有父级编号',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '名称',
`sort` decimal(10,0) NOT NULL COMMENT '排序',
`icon` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '图标',
`is_show` char(1) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '是否在菜单中显示',
`permission` varchar(200) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '权限标识',
`create_by` bigint(20) NOT NULL COMMENT '创建者',
`create_date` datetime NOT NULL COMMENT '创建时间',
`update_by` bigint(20) NOT NULL COMMENT '更新者',
`update_date` datetime NOT NULL COMMENT '更新时间',
`remarks` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '备注信息',
`del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '0' COMMENT '删除标记',
`keyval` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'key',
`type` tinyint(10) NOT NULL DEFAULT '0' COMMENT '菜单类型 0菜单 1按钮',
`href` varchar(60) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '链接',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';
用户角色表
CREATE TABLE `sys_user_role` (
`user_id` bigint(20) NOT NULL COMMENT '用户编号',
`role_id` bigint(20) NOT NULL COMMENT '角色编号',
PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色表';
角色权限表
CREATE TABLE `sys_role_menu` (
`role_id` bigint(20) NOT NULL COMMENT '角色编号',
`menu_id` bigint(20) NOT NULL COMMENT '菜单编号',
PRIMARY KEY (`role_id`,`menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限表';
表关系
表数据
INSERT INTO `sys_user` VALUES (1, '王文龙', 26, '男', '17621298039', 'https://wx.qlogo.cn/mmopen/vi_32/OvXNATSNlFlibKP2U9B3ibYibSSbeMlab9lQcTUqMcPKtBOb51x2EaXjjKUXibIPLia3Mibia8zwZ3EaXHibvzQFa5qJaw/132', NULL, NULL, NULL, 'MrWang', '$2a$10$3Fqf02GaMP4j8PClWrlnPu0goXPRoT7ymGlFZPdP9JFlQNaPoaScC', NULL);
INSERT INTO `sys_user` VALUES (2, '范冰冰', 26, '女', '17621298039', 'https://wx.qlogo.cn/mmopen/vi_32/OvXNATSNlFlibKP2U9B3ibYibSSbeMlab9lQcTUqMcPKtBOb51x2EaXjjKUXibIPLia3Mibia8zwZ3EaXHibvzQFa5qJaw/132', NULL, NULL, NULL, 'fanbingbing', '$2a$10$3Fqf02GaMP4j8PClWrlnPu0goXPRoT7ymGlFZPdP9JFlQNaPoaScC', NULL);
INSERT INTO `sys_role` VALUES (1, NULL, '系统管理员', NULL, NULL, NULL, NULL, NULL, '王文龙', '2020-07-08 10:20:43', '', '2020-07-08 10:21:46', NULL, '1');
INSERT INTO `sys_role` VALUES (2, NULL, '普通用户', NULL, NULL, NULL, NULL, NULL, '王文龙', '2020-07-08 10:20:43', '', '2020-07-08 10:21:46', NULL, '1');
INSERT INTO `sys_menu` VALUES (1, 0, '0', '用户列表', 1, NULL, '1', 'sys:user:findAll', 1, '2020-07-08 10:24:50', 1, '2020-07-08 10:25:13', NULL, '1', '1', 1, NULL);
INSERT INTO `sys_menu` VALUES (2, 0, '0', 'id查询', 1, NULL, '1', 'sys:user:findById', 1, '2020-07-08 10:24:50', 1, '2020-07-08 10:25:13', NULL, '1', '1', 1, NULL);
INSERT INTO `sys_user_role` VALUES (1, 1);
INSERT INTO `sys_user_role` VALUES (2, 2);
INSERT INTO `sys_role_menu` VALUES (1, 1);
INSERT INTO `sys_role_menu` VALUES (1, 2);
INSERT INTO `sys_role_menu` VALUES (2, 2);
2:Pom依赖
<!--Security依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT依赖 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
3:application.yml 配置
#Tomcat配置
server:
port: 10922
# # # Mysql数据库配置 # # # # # # Mysql数据库配置 # # # # # # Mysql数据库配置 # # # # # # Mysql数据库配置 # # #
spring:
# 配置数据源
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/conformity?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: root1234
# type: com.alibaba.druid.pool.DruidDataSource
# # # jwt 配置 # # # # # # jwt 配置 # # # # # # jwt 配置 # # # # # # jwt 配置 # # # # # # jwt 配置 # # #
jwt:
# 密匙KEY
secret: JWTSecret
# HeaderKEY
tokenHeader: Authorization
# Token前缀字符
tokenPrefix: Sans-
# 过期时间 单位秒 1天后过期=86400 7天后过期=604800
expiration: 86400
# 配置不需要认证的接口
antMatchers: /index/**,/login/**,/favicon.ico
# # # mybatis配置 # # # # mybatis配置 # # # # mybatis配置 # # # # mybatis配置 # # # # mybatis配置 # # # # mybatis配置 # #
##自动转驼峰
mybatis:
configuration:
map-underscore-to-camel-case: true
4:Jwt工具(生成token),及工具类
4.1、jwt配置类
package com.it.conformity.common.config;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* jwt配置类
*
* @Author: 王文龙
* @Date: 2020/7/316:08
* @Version: 1.0
* @Describe: 描述:
*/
@Getter
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtConfig {
// @Value( "${jwt.secret}")
public static String secret;
// @Value("${jwt.tokenHeader}")
public static String tokenHeader;//HeaderKey
// @Value( "${jwt.tokenPrefix}")
public static String tokenPrefix;//Token前缀字符
// @Value( "${jwt.expiration}")
public static Integer expiration;//过期时间 单位秒 1天后过期=86400 7天后过期=604800
// @Value( "${jwt.antMatchers}")
public static String antMatchers;//配置不需要认证的接口
public void setSecret(String secret) {
this.secret = secret;
}
public void setTokenHeader(String tokenHeader) {
this.tokenHeader = tokenHeader;
}
public void setTokenPrefix(String tokenPrefix) {
this.tokenPrefix = tokenPrefix;
}
public void setExpiration(Integer expiration) {
this.expiration = expiration * 1000;
}
public void setAntMatchers(String antMatchers) {
this.antMatchers = antMatchers;
}
}
4.2、jwt 工具类
package com.it.conformity.common.util;
import com.alibaba.fastjson.JSON;
import com.it.conformity.common.config.JwtConfig;
import com.it.conformity.security.entity.SelfUserEntity;
import com.it.conformity.sys.pojo.SysUser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;
/**
* jwt 工具类
*
* @Author: 王文龙
* @Date: 2020/7/314:10
* @Version: 1.0
* @Describe: 描述:
*/
@Slf4j
public class JwtTokenUtil {
/**
* 生成token
*
* @param sysUser 用户实体类
* @return token
*/
public static String createToken(SelfUserEntity sysUser) {
//登录成功生成token
//Jwts.builder()底层代码 new 了一个 DefaultJwtBuilder 对象,它是wtBuilder的实现类
String token = Jwts.builder()
//放入用户名和id
.setId(sysUser.getUserId() + "")
//主题
.setSubject(sysUser.getUsername())
//签发时间
.setIssuedAt(new Date())
//签发者
.setIssuer(sysUser.getUsername())
//自定义属性,嵌入用户拥有权限
.claim("authorities", JSON.toJSONString(sysUser.getAuthorities()))
//失效时间
.setExpiration(new Date(System.currentTimeMillis() + JwtConfig.expiration))
//签名算法和密钥
.signWith(SignatureAlgorithm.HS512, JwtConfig.secret)
.compact();
return token;
}
}
4.3、无权限处理类
package com.it.conformity.security.handler;
import com.it.conformity.common.util.JsonResultT;
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;
import java.util.HashMap;
import java.util.Map;
/**
* 无权限 处理类
*
* @Author: 王文龙
* @Date: 2020/7/316:50
* @Version: 1.0
* @Describe: 描述:
*/
@Component
public class UserAuthAccessDeniedHandler implements AccessDeniedHandler {
/**
* 重写 handle 方法
*
* @param httpServletRequest 请求
* @param httpServletResponse 相应
* @param e 异常
* @throws IOException Io异常
* @throws ServletException Servlet异常
*/
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
Map<String, Object> map = new HashMap<>();
map.put("code", 403);
map.put("msg", "未授权");
JsonResultT.responseJson(httpServletResponse, map);
}
}
4.4、未登录处理类
package com.it.conformity.security.handler;
import com.it.conformity.common.util.JsonResultT;
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;
import java.util.HashMap;
import java.util.Map;
/**
* 用户未登录处理类
* @Author: 王文龙
* @Date: 2020/7/610:06
* @Version: 1.0
* @Describe: 描述:
*/
@Component
public class UserAuthenticationEntryPointHandler implements AuthenticationEntryPoint {
/**
*
* @param httpServletRequest 导致了<code>身份验证异常
* @param httpServletResponse 以便用户代理可以开始身份验证
* @param e 引发了这场召唤
* @throws IOException 抛异常
* @throws ServletException 抛异常
*/
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
Map<String, Object> map = new HashMap<>();
map.put("code", 401);
map.put("msg", "未登录");
JsonResultT.responseJson(httpServletResponse, map);
}
}
4.5、登录失败处理类
package com.it.conformity.security.handler;
import com.it.conformity.common.util.JsonResultT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 登录失败处理类
*
* @Author: 王文龙
* @Date: 2020/7/610:31
* @Version: 1.0
* @Describe: 描述:
*/
@Slf4j
@Component
public class UserLoginFailureHandler implements AuthenticationFailureHandler {
/**
* 当身份验证尝试失败时调用
*
* @param request 进行身份验证尝试的请求
* @param response 响应
* @param exception 为拒绝身份验证而引发的异常
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
//这些对于操作的处理类可以根据不同异常进行不同处理
if (exception instanceof UsernameNotFoundException) {
log.info("【登录失败】" + exception.getMessage());
Map<String, Object> map = new HashMap<>();
map.put("code", 500);
map.put("msg", "用户名不存在");
JsonResultT.responseJson(response, map);
}
if (exception instanceof LockedException) {
log.info("【登陆失败】" + exception.getMessage());
Map<String, Object> map = new HashMap<>();
map.put("code", 500);
map.put("msg", "用户被冻结");
JsonResultT.responseJson(response, map);
}
if (exception instanceof BadCredentialsException) {
log.info("【登陆失败】" + exception.getMessage());
Map<String, Object> map = new HashMap<>();
map.put("code", 500);
map.put("msg", "用户名密码不正确");
JsonResultT.responseJson(response, map);
}
Map<String, Object> map = new HashMap<>();
map.put("code", 500);
map.put("msg", "登录失败");
JsonResultT.responseJson(response, map);
}
}
4.6、登录成功处理类
package com.it.conformity.security.handler;
import com.alibaba.fastjson.JSON;
import com.it.conformity.common.config.JwtConfig;
import com.it.conformity.common.util.JwtTokenUtil;
import com.it.conformity.common.util.JsonResultT;
import com.it.conformity.security.entity.SelfUserEntity;
import com.it.conformity.sys.pojo.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 登录成功处理类
*
* @Author: 王文龙
* @Date: 2020/7/610:44
* @Version: 1.0
* @Describe: 描述:
*/
@Slf4j
@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {
/**
* 当用户成功通过身份验证时调用
*
* @param request 导致成功身份验证的请求
* @param response 响应
* @throws IOException 抛异常
* @throws ServletException 抛异常
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
// 组装JWT
SelfUserEntity user = (SelfUserEntity) authentication.getPrincipal();
String token = JwtTokenUtil.createToken(user);
token = JwtConfig.tokenPrefix + token;
// 封装返回参数
Map<String, Object> resultData = new HashMap<>();
resultData.put("code", "200");
resultData.put("msg", "登录成功");
resultData.put("token", token);
JsonResultT.responseJson(response, resultData);
}
}
4.7、用户登出类
package com.it.conformity.security.handler;
import com.it.conformity.common.util.JsonResultT;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 用户登出类
*
* @Author: 王文龙
* @Date: 2020/7/610:55
* @Version: 1.0
* @Describe: 描述:
*/
@Component
public class UserLogoutSuccessHandler implements LogoutSuccessHandler {
/**
* 用户登出返回结果,应该让前台消除token
*
* @throws IOException 抛异常
* @throws ServletException 抛异常
*/
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Map<String, Object> resultData = new HashMap<>();
resultData.put("code", "200");
resultData.put("msg", "登出成功");
SecurityContextHolder.clearContext();
JsonResultT.responseJson(response, resultData);
}
}
5:Security核心类
5.1、SpringSecurity用户的实体
package com.it.conformity.security.entity;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.util.Collection;
/**
* SpringSecurity用户的实体
* 注意:这里必须要实现UserDetails接口
*
* @Author: 王文龙
* @Date: 2020/7/611:43
* @Version: 1.0
* @Describe: 描述:
*/
@Data
public class SelfUserEntity implements Serializable, UserDetails {
// private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Integer userId;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 状态:NORMAL正常 PROHIBIT禁用
*/
private String status;
/**
* 用户角色
*/
private Collection<GrantedAuthority> authorities;
/**
* 账户是否过期
*/
private boolean isAccountNonExpired = false;
/**
* 账户是否被锁定
*/
private boolean isAccountNonLocked = false;
/**
* 证书是否过期
*/
private boolean isCredentialsNonExpired = false;
/**
* 账户是否有效
*/
private boolean isEnabled = true;
@Override
public Collection<GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return isAccountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return isAccountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return isCredentialsNonExpired;
}
@Override
public boolean isEnabled() {
return isEnabled;
}
}
5.2、SpringSecurity用户的业务实现
package com.it.conformity.security.service;
import com.it.conformity.security.entity.SelfUserEntity;
import com.it.conformity.sys.pojo.SysUser;
import com.it.conformity.sys.service.SysUserService;
import org.springframework.beans.BeanUtils;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* SpringSecurity用户的业务实现
*
* @Author: 王文龙
* @Date: 2020/7/611:43
* @Version: 1.0
* @Describe: 描述:
*/
@Component
public class SelfUserDetailsServiceImpl implements UserDetailsService {
@Resource
private SysUserService sysUserService;
/**
* 查询用户信息
*
* @param username 用户名
* @return UserDetails
* @throws UsernameNotFoundException 抛异常
*/
@Override
public SelfUserEntity loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser userName = sysUserService.findByUserName(username);
if (userName != null) {
// 组装参数
SelfUserEntity selfUserEntity = new SelfUserEntity();
BeanUtils.copyProperties(userName, selfUserEntity);
selfUserEntity.setStatus(userName.getEnabled());
selfUserEntity.setUserId(userName.getId());
return selfUserEntity;
}
return null;
}
}
5.3、自定义登录验证
package com.it.conformity.security;
import com.it.conformity.security.entity.SelfUserEntity;
import com.it.conformity.security.service.SelfUserDetailsServiceImpl;
import com.it.conformity.sys.dao.SysUserRoleDao;
import com.it.conformity.sys.pojo.SysRole;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 自定义登录验证
*
* @Author: 王文龙
* @Date: 2020/7/611:17
* @Version: 1.0
* @Describe: 描述:
*/
@Component
public class UserAuthenticationProvider implements AuthenticationProvider {
@Resource
private SelfUserDetailsServiceImpl selfUserDetailsService;
@Resource
private SysUserRoleDao sysUserRoleDao;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//1、获取表单输入的用户名
String username = (String) authentication.getPrincipal();
//2、获取表单输入的密码
String password = (String) authentication.getCredentials();
//3、查询用户是否存在
SelfUserEntity userEntity = selfUserDetailsService.loadUserByUsername(username);
if (userEntity == null) {
throw new UsernameNotFoundException("用户名不存在");
}
// 我们还要判断密码是否正确,这里我们的密码使用BCryptPasswordEncoder进行加密的
if (!new BCryptPasswordEncoder().matches(password, userEntity.getPassword())) {
throw new BadCredentialsException("密码不正确");
}
// 还可以加一些其他信息的判断,比如用户账号已停用等判断
String enabled = "0";
if (enabled.equals(userEntity.getStatus())) {
throw new LockedException("该用户已被冻结");
}
//查询用户角色
Set<GrantedAuthority> authorities = new HashSet<>();
List<SysRole> sysRoles = sysUserRoleDao.selectSysRoleByUserId(userEntity.getUserId());
for (SysRole sysRole : sysRoles) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + sysRole.getName()));
}
userEntity.setAuthorities(authorities);
//进行登录
return new UsernamePasswordAuthenticationToken(userEntity, password, authorities);
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
5.4、自定义权限注解验证
package com.it.conformity.security;
import com.it.conformity.security.entity.SelfUserEntity;
import com.it.conformity.sys.dao.SysMenuDao;
import com.it.conformity.sys.pojo.SysMenu;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 自定义权限注解验证
*
* @Author: 王文龙
* @Date: 2020/7/615:29
* @Version: 1.0
* @Describe: 描述:
*/
@Component
public class UserPermissionEvaluator implements PermissionEvaluator {
@Resource
private SysMenuDao sysMenuDao;
/**
* hasPermission鉴权方法
* 这里仅仅判断PreAuthorize注解中的权限表达式
* 实际中可以根据业务需求设计数据库通过targetUrl和permission做更复杂鉴权
*
* @Param authentication 用户身份
* @Param targetUrl 请求路径
* @Param permission 请求路径权限
* @Return boolean 是否通过
*/
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
//获取用户信息
SelfUserEntity selfUserEntity = (SelfUserEntity) authentication.getPrincipal();
// 查询用户权限(这里可以将权限放入缓存中提升效率)
Set<String> permissions = new HashSet<>();
List<SysMenu> sysMenus = sysMenuDao.selectMenuByUserId(selfUserEntity.getUserId());
for (SysMenu sysMenu : sysMenus) {
permissions.add(sysMenu.getPermission());
}
// 权限对比
if (permissions.contains(permission.toString())) {
return true;
}
return false;
}
/**
* Alternative method for evaluating a permission where only the identifier of the
* target object is available, rather than the target instance itself.
*
* @param authentication represents the user in question. Should not be null.
* @param targetId the identifier for the object instance (usually a Long)
* @param targetType a String representing the target's type (usually a Java
* classname). Not null.
* @param permission a representation of the permission object as supplied by the
* expression system. Not null.
* @return true if the permission is granted, false otherwise
*/
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
return false;
}
}
5.5、SpringSecurity核心配置类
package com.it.conformity.common.config;
import com.it.conformity.security.UserAuthenticationProvider;
import com.it.conformity.security.UserPermissionEvaluator;
import com.it.conformity.security.handler.*;
import com.it.conformity.security.jwt.JwtAuthenticationTokenFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import javax.annotation.Resource;
/**
* SpringSecurity核心配置类
*
* @Author: 王文龙
* @Date: 2020/7/714:47
* @Version: 1.0
* @Describe: 描述:
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限注解,默认是关闭的
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserLoginSuccessHandler userLoginSuccessHandler;//自定义成功处理类
@Resource
private UserLoginFailureHandler userLoginFailureHandler;//自定义登录失败处理类
@Resource
private UserLogoutSuccessHandler userLogoutSuccessHandler;//自定义注销成功处理类
@Resource
private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;//自定义无权访问处理类
@Resource
private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;//自定义用户未登录处理类
@Resource
private UserAuthenticationProvider userAuthenticationProvider;//自定义登录验证
/**
* 加密方式
*
* @return BCryptPasswordEncoder
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 注入自定义 permissionEvaluator
*
* @return handler
*/
@Bean
public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler() {
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(new UserPermissionEvaluator());
return handler;
}
/**
* 配置登录验证逻辑
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) {
//这里可启用我们自己的登陆验证逻辑
auth.authenticationProvider(userAuthenticationProvider);
}
/**
* 配置security的控制逻辑
* @param http http 请求
* @throws Exception 异常
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//不进行权限验证的请求或资源(从配置文件中读取)
.antMatchers(JwtConfig.antMatchers.split(",")).permitAll()
//其他的需要登陆后才能访问
.anyRequest().authenticated()
.and()
//配置未登录自定义处理类
.httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler)
.and()
//配置登录地址
.formLogin()
.loginProcessingUrl("/login/userLogin")
//配置登录成功自定义处理类
.successHandler(userLoginSuccessHandler)
//配置登录失败自定义处理类
.failureHandler(userLoginFailureHandler)
.and()
//配置登出地址
.logout()
.logoutUrl("/login/userLogout")
//配置用户登出自定义处理类
.logoutSuccessHandler(userLogoutSuccessHandler)
.and()
//配置没有权限自定义处理类
.exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler)
.and()
// 开启跨域
.cors()
.and()
// 取消跨站请求伪造防护
.csrf().disable();
// 基于Token不需要session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 禁用缓存
http.headers().cacheControl();
// 添加JWT过滤器
http.addFilter(new JwtAuthenticationTokenFilter(authenticationManager()));
}
}
5.6、JWT接口请求校验过滤器
package com.it.conformity.security.jwt;
import com.alibaba.fastjson.JSONObject;
import com.it.conformity.common.config.JwtConfig;
import com.it.conformity.security.entity.SelfUserEntity;
import com.it.conformity.common.util.StringUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* JWT接口请求校验过滤器
* @Author: 王文龙
* @Date: 2020/7/715:12
* @Version: 1.0
* @Describe: 描述:
*/
@Slf4j
public class JwtAuthenticationTokenFilter extends BasicAuthenticationFilter {
public JwtAuthenticationTokenFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 获取请求头中JWT的Token
String tokenHeader = request.getHeader(JwtConfig.tokenHeader);
// startsWith方法是String的方法,用于检查字符串以指定的前缀开头
if (null!=tokenHeader && tokenHeader.startsWith(JwtConfig.tokenPrefix)) {
try {
// 截取JWT前缀
String token = tokenHeader.replace(JwtConfig.tokenPrefix, "");
// 解析JWT
Claims claims = Jwts.parser()
.setSigningKey(JwtConfig.secret)//base64加密
.parseClaimsJws(token)
.getBody();
// 获取用户名
String username = claims.getSubject();
String userId=claims.getId();
if(!StringUtils.isEmpty(username)&&!StringUtils.isEmpty(userId)) {
// 获取角色
List<GrantedAuthority> authorities = new ArrayList<>();
String authority = claims.get("authorities").toString();
if(!StringUtils.isEmpty(authority)){
List<Map<String,String>> authorityMap = JSONObject.parseObject(authority, List.class);
for(Map<String,String> role : authorityMap){
if(!org.springframework.util.StringUtils.isEmpty(role)) {
authorities.add(new SimpleGrantedAuthority(role.get("authority")));
}
}
}
//组装参数
SelfUserEntity selfUserEntity = new SelfUserEntity();
selfUserEntity.setUsername(claims.getSubject());
selfUserEntity.setUserId(Integer.parseInt(claims.getId()));
selfUserEntity.setAuthorities(authorities);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(selfUserEntity, userId, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (ExpiredJwtException e){
log.info("Token过期");
} catch (Exception e) {
log.info("Token无效");
}
}
filterChain.doFilter(request, response);
return;
}
}
6、Security流程图
1、SpringSecurity核心配置(安全认证策略)
2、登录认证
3、权限验证
7、测试
到此为止上面的配置就算是结束了,现在可以测试了,还有一点值得提醒的是,Springboot的启动类,没用的扫描或注解最好去掉,不然会报错
1、登录测试
2、接口测试
这里以 findById和findAll俩接口进行测试,库表中有两个用户,两个角色,两个权限
关系:
用户 1,管理员,可以同时访问findById和findAll两个接口(相当于最大权限)
用户 2,普通用户,只可访问findById接口,如果访问findAll接口 则会提示 权限不足
添加权限:
需要在接口上添加@PreAuthorize注解,将权限的permission信息配置到注解中即可
看到这一步,你也应该跑起来,并且实现了吧,剩下一点小尾巴,来说一下获取当前登录人
SpringSecurity的核心组件是SecurityContextHolder它用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保存在SecurityContextHolder中。SecurityContextHolder
默认使用ThreadLocal
策略来存储认证信息。看到ThreadLocal
也就意味着,这是一种与线程绑定的策略。Spring Security在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息
自定义UserUtils
package com.it.conformity.common.util;
import com.it.conformity.security.entity.SelfUserEntity;
import com.it.conformity.sys.dao.SysUserDao;
import com.it.conformity.sys.pojo.SysUser;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @Author: 王文龙
* @Date: 2020/7/916:30
* @Version: 1.0
* @Describe: 描述:
*/
@Component
public class UserUtils {
@Resource
public SysUserDao sysUserDao;
public static SysUser getUser() {
SelfUserEntity sysUser = (SelfUserEntity)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return sysUserDao.findById(sysUser.getUserId());
}
}
用法
SysUser user = UserUtils.getUser();