1、SpringSecurity简介:
Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。2、基本功能:
主要功能是“认证”和“授权”,Web 应用的安全性包括**用户认证(Authentication)和用户授权(Authorization)**两个部分,这两点也是 Spring Security 重要核心功能。用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录
用户授权指的是:验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。3、特点:
和 Spring 无缝整合。
全面的权限控制。
专门为 Web 开发而设计。
重量级
认证流程:
理论部分就不一一解释了,直接上项目:
目录
程序搭建:开始
添加数据库连接:application.yml
添加数据库数据MySQL+Redis:
集成knife4j开发文档类:knife4j
Redis相关配置类:3个基础类
设置允许跨域
统一返回前端数据类
JWT工具类:
字符串渲染到客户端WebUtils类:
springsecurity相关配置开始:
业务编写
登录注册模块
项目整体结构
项目Gitee地址
添加pom依赖:保证能让项目正常跑起来
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.5.7
com.demo.springSecurity
xiaoyang
0.0.1-SNAPSHOT
xiaoyang
xiaoyang
1.8
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
runtime
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-data-redis
com.alibaba
fastjson
1.2.33
io.jsonwebtoken
jjwt
0.9.0
com.baomidou
mybatis-plus-boot-starter
3.4.3
com.github.xiaoymin
knife4j-spring-boot-starter
3.0.3
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
server:
port: 8888
spring:
datasource:
url: jdbc:mysql://localhost:3306/springsecurity?characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: 127.0.0.1
timeout: 1000
jedis:
pool:
min-idle: 5
max-idle: 10
max-wait: -1
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
找到该目录下的SQL文件,拷贝到数据运行即可
package com.demo.springSecurity.utils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class Knife4jConfiguration {
@Bean(value = "defaultApi2")
/**
* 根据自己的需求修改信息
*/
public Docket defaultApi2() {
Docket docket=new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
.title("SpringSecurity")
.description("# swagger-bootstrap-ui-demo RESTful APIs")
.termsOfServiceUrl("http://www.xxx.com/")
.contact(new Contact("yyds","","[email protected]"))
.version("1.8")
.build())
//分组名称
.groupName("2.X版本")
.select()
.apis(RequestHandlerSelectors.basePackage("com.demo.springSecurity"))//这里指定Controller扫描包路径
.paths(PathSelectors.any())
.build();
return docket;
}
}
Redis使用FastJson序列化
package com.demo.springSecurity.redis;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.Charset;
/**
* Redis使用FastJson序列化
*
* @author sg
*/
public class FastJsonRedisSerializer implements RedisSerializer
{
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class clazz;
static
{
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
public FastJsonRedisSerializer(Class clazz)
{
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException
{
if (t == null)
{
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException
{
if (bytes == null || bytes.length <= 0)
{
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz);
}
protected JavaType getJavaType(Class> clazz)
{
return TypeFactory.defaultInstance().constructType(clazz);
}
}
redis基础配置类,提供基础的简便化编写数据
package com.demo.springSecurity.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* redis基础配置类,提供基础的简便化编写数据
*/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public T getCacheObject(final String key)
{
ValueOperations operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection)
{
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public long setCacheList(final String key, final List dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public List getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public BoundSetOperations setCacheSet(final String key, final Set dataSet)
{
BoundSetOperations setOperation = redisTemplate.boundSetOps(key);
Iterator it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public Set getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public void setCacheMap(final String key, final Map dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public Map getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public T getCacheMapValue(final String key, final String hKey)
{
HashOperations opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 删除Hash中的数据
*
* @param key
* @param hkey
*/
public void delCacheMapValue(final String key, final String hkey)
{
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public List getMultiCacheMapValue(final String key, final Collection
存取Redis数据序列化,作用查看Redis数据时直观,避免乱码显示
package com.demo.springSecurity.redis;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* 存取Redis数据序列化,作用查看Redis数据时直观,避免乱码显示
*/
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate
package com.demo.springSecurity.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路径
registry.addMapping("/**")
// 设置允许跨域请求的域名
.allowedOriginPatterns("*")
// 是否允许cookie
.allowCredentials(true)
// 设置允许的请求方式
.allowedMethods("GET", "POST", "DELETE", "PUT")
// 设置允许的header属性
.allowedHeaders("*")
// 跨域允许时间
.maxAge(3600);
}
}
package com.demo.springSecurity.utils;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* 统一数据返回
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class R {
/**
* 状态码
*/
private Integer code;
/**
* 提示信息,如果有错误时,前端可以获取该字段进行提示
*/
private String msg;
/**
* 查询到的结果数据,
*/
private T data;
public R(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public R(Integer code, T data) {
this.code = code;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public R(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
package com.demo.springSecurity.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
/**
* JWT工具类
*/
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "yyds";
public static String getUUID(){
String token = UUID.randomUUID().toString().replaceAll("-", "");
return token;
}
/**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @return
*/
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
return builder.compact();
}
/**
* 生成jtw
* @param subject token中要存放的数据(json格式)
* @param ttlMillis token超时时间
* @return
*/
public static String createJWT(String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if(ttlMillis==null){
ttlMillis=JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("ytt") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
.setExpiration(expDate);
}
/**
* 创建token
* @param id
* @param subject
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
return builder.compact();
}
public static void main(String[] args) throws Exception {
// String jwt = createJWT("2123");
Claims claims = parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyOTY2ZGE3NGYyZGM0ZDAxOGU1OWYwNjBkYmZkMjZhMSIsInN1YiI6IjIiLCJpc3MiOiJzZyIsImlhdCI6MTYzOTk2MjU1MCwiZXhwIjoxNjM5OTY2MTUwfQ.NluqZnyJ0gHz-2wBIari2r3XpPp06UMn4JS2sWHILs0");
String subject = claims.getSubject();
System.out.println(subject);
// System.out.println(claims);
}
/**
* 生成加密后的秘钥 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
package com.demo.springSecurity.utils;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class WebUtils
{
/**
* 将字符串渲染到客户端
*
* @param response 渲染对象
* @param string 待渲染的字符串
* @return null
*/
public static String renderString(HttpServletResponse response, String string) {
try
{
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
}
catch (IOException e)
{
e.printStackTrace();
}
return null;
}
}
JwtAuthenticationTokenFilter
package com.demo.springSecurity.security.config;
import com.demo.springSecurity.redis.RedisCache;
import com.demo.springSecurity.sys.entity.LoginUser;
import com.demo.springSecurity.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
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;
import java.util.Objects;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisCache redisCache;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
//放行
filterChain.doFilter(request, response);
return;
}
//解析token
String userid;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("token非法");
}
//从redis中获取用户信息
String redisKey =userid;
LoginUser loginUser = redisCache.getCacheObject(redisKey);
if(Objects.isNull(loginUser)){
throw new RuntimeException("用户未登录");
}
//存入SecurityContextHolder
//TODO 获取权限信息封装到Authentication中
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request, response);
}
}
SecurityConfig
package com.demo.springSecurity.security.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.WebSecurityConfigurerAdapter;
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.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
//创建BCryptPasswordEncoder注入容器
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
//knife4j文档放行
.antMatchers("/doc.html", "/swagger-resources/**", "/v2/api-docs", "/v2/api-docs-ext",
"/webjars/**" ).anonymous()
.antMatchers("/user/login").anonymous()
.antMatchers("/reg/userReg").anonymous()
.antMatchers("/test/hello").anonymous()
// .antMatchers("/testCors").hasAuthority("system:dept:list222")
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
//添加过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
//配置异常处理器
http.exceptionHandling()
//配置认证失败处理器
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
//允许跨域
http.cors();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
AccessDeniedHandlerImpl
package com.demo.springSecurity.security.handler;
import com.alibaba.fastjson.JSON;
import com.demo.springSecurity.utils.R;
import com.demo.springSecurity.utils.WebUtils;
import org.springframework.http.HttpStatus;
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;
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
R result = new R(HttpStatus.FORBIDDEN.value(),"您的权限不足");
String json = JSON.toJSONString(result);
//处理异常
WebUtils.renderString(response,json);
}
}
AuthenticationEntryPointImpl
package com.demo.springSecurity.security.handler;
import com.alibaba.fastjson.JSON;
import com.demo.springSecurity.utils.R;
import com.demo.springSecurity.utils.WebUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
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;
@Component
@Slf4j
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
R r = new R(HttpStatus.UNAUTHORIZED.value(),"用户认证失败请查询登录");
String json = JSON.toJSONString(r);
//处理异常
WebUtils.renderString(response,json);
log.info("用户认证失败请查询登录");
}
}
SGExpressionRoot
package com.demo.springSecurity.security.handler;
import com.demo.springSecurity.sys.entity.LoginUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.List;
@Component("ex")
public class SGExpressionRoot {
public boolean hasAuthority(String authority){
//获取当前用户的权限
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
List permissions = loginUser.getPermissions();
//判断用户权限集合中是否存在authority
return permissions.contains(authority);
}
}
SGFailureHandler
package com.demo.springSecurity.security.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
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;
@Component
@Slf4j
public class SGFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.info("=================");
System.out.println("认证失败了");
}
}
SGLogoutSuccessHandler
package com.demo.springSecurity.security.handler;
import org.springframework.security.core.Authentication;
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;
@Component
public class SGLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("注销成功");
}
}
SGSuccessHandler
package com.demo.springSecurity.security.handler;
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;
@Component
public class SGSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("认证成功了");
}
}
LoginUser实体类
package com.demo.springSecurity.sys.entity;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
private List permissions;
public LoginUser(User user, List permissions) {
this.user = user;
this.permissions = permissions;
}
@JSONField(serialize = false)
private List authorities;
@Override
public Collection extends GrantedAuthority> getAuthorities() {
if(authorities!=null){
return authorities;
}
//把permissions中String类型的权限信息封装成SimpleGrantedAuthority对象
authorities = new ArrayList<>();
for (String permission : permissions) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
authorities.add(authority);
}
// authorities = permissions.stream()
// .map(SimpleGrantedAuthority::new)
// .collect(Collectors.toList());
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
Menu实体类
package com.demo.springSecurity.sys.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 菜单表(Menu)实体类
*/
@TableName(value="sys_menu")
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Menu implements Serializable {
private static final long serialVersionUID = -54979041104113736L;
@TableId
private Long id;
/**
* 菜单名
*/
private String menuName;
/**
* 路由地址
*/
private String path;
/**
* 组件路径
*/
private String component;
/**
* 菜单状态(0显示 1隐藏)
*/
private String visible;
/**
* 菜单状态(0正常 1停用)
*/
private String status;
/**
* 权限标识
*/
private String perms;
/**
* 菜单图标
*/
private String icon;
private Long createBy;
private Date createTime;
private Long updateBy;
private Date updateTime;
/**
* 是否删除(0未删除 1已删除)
*/
private Integer delFlag;
/**
* 备注
*/
private String remark;
}
SysUserRole权限实体类
package com.demo.springSecurity.sys.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@TableName(value="sys_user_role")
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SysUserRole {
// `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户id',
// `role_id` bigint NOT NULL DEFAULT '0' COMMENT '角色id',
private int userId;
private int roleId;
}
User用户数据实体类
package com.demo.springSecurity.sys.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 用户表(User)实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("sys_user")
public class User implements Serializable {
private static final long serialVersionUID = -40356785423868312L;
/**
* 主键
*/
@TableId
private Long id;
/**
* 用户名
*/
private String userName;
/**
* 昵称
*/
private String nickName;
/**
* 密码
*/
private String password;
/**
* 账号状态(0正常 1停用)
*/
private String status;
/**
* 邮箱
*/
private String email;
/**
* 手机号
*/
private String phonenumber;
/**
* 用户性别(0男,1女,2未知)
*/
private String sex;
/**
* 头像
*/
private String avatar;
/**
* 用户类型(0管理员,1普通用户)
*/
private String userType;
/**
* 创建人的用户id
*/
private Long createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新人
*/
private Long updateBy;
/**
* 更新时间
*/
private Date updateTime;
/**
* 删除标志(0代表未删除,1代表已删除)
*/
private Integer delFlag;
}
MenuMapper层
package com.demo.springSecurity.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.demo.springSecurity.sys.entity.Menu;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface MenuMapper extends BaseMapper
对应的xml文件
UserMapper层
package com.demo.springSecurity.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.demo.springSecurity.sys.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper {
/**
* 根据用户名查询用户数据 该方法用于注册成功添加权限
*/
User findName(String username);
}
对用的xml文件
SysUserRoleMapper层
package com.demo.springSecurity.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.demo.springSecurity.sys.entity.SysUserRole;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface SysUserRoleMapper extends BaseMapper {
}
LoginServcie接口类
package com.demo.springSecurity.sys.service;
import com.demo.springSecurity.utils.R;
public interface LoginServcie {
/**
* 用户登录
* @param
* @return
*/
R login(String username, String password);
/**
* 用户登出 删除Redis与过滤器相关数据
* @return
*/
R logout();
}
LoginServcie实现类
package com.demo.springSecurity.sys.service.Impl;
;
import com.demo.springSecurity.redis.RedisCache;
import com.demo.springSecurity.sys.entity.LoginUser;
import com.demo.springSecurity.sys.service.LoginServcie;
import com.demo.springSecurity.utils.JwtUtil;
import com.demo.springSecurity.utils.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Service
public class LoginServiceImpl implements LoginServcie {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisCache redisCache;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public R login(String username ,String password) {
//AuthenticationManager authenticate进行用户认证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,password);
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//如果认证没通过,给出对应的提示
if(Objects.isNull(authenticate)){
throw new RuntimeException("登录失败");
}
//如果认证通过了,使用userid生成一个jwt jwt存入ResponseResult返回
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userid = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userid);
Map map = new HashMap<>();
map.put("token",jwt);
//把完整的用户信息存入redis userid作为key
redisCache.setCacheObject(userid,loginUser);
return new R(200,"登录成功",map);
}
@Override
public R logout() {
//获取SecurityContextHolder中的用户id
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
Long userid = loginUser.getUser().getId();
//删除redis中的值
redisCache.deleteObject("login:"+userid);
return new R(200,"注销成功");
}
}
UserDetailsServiceImpl查询用户业务层
package com.demo.springSecurity.sys.service.Impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.demo.springSecurity.sys.entity.LoginUser;
import com.demo.springSecurity.sys.entity.User;
import com.demo.springSecurity.sys.mapper.UserMapper;
import com.demo.springSecurity.sys.mapper.MenuMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Autowired
private MenuMapper menuMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询用户信息
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName,username);
User user = userMapper.selectOne(queryWrapper);
//如果没有查询到用户就抛出异常
if(Objects.isNull(user)){
throw new RuntimeException("用户名或者密码错误");
}
// List list = new ArrayList<>(Arrays.asList("test","admin"));
List list = menuMapper.selectPermsByUserId(user.getId());
//把数据封装成UserDetails返回
return new LoginUser(user,list);
}
}
LoginController登录模块
package com.demo.springSecurity.sys.controller;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.demo.springSecurity.sys.service.LoginServcie;
import com.demo.springSecurity.utils.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@Api(tags = "登录模块")
@RestController
@RequestMapping("/user")
public class LoginController {
@Autowired
private LoginServcie loginServcie;
/**
* 用户登录
* @param
* @return
*/
@ApiOperationSupport(author = "admin")
@ApiOperation(value = "用户登录")
@PostMapping("/login")
public R login(@RequestParam String username,@RequestParam String password){
//登录
System.out.println("用户登录成功》》》》》》》");
return loginServcie.login(username,password);
}
/**
* 用户登出 同时删除Redis缓存的token
* @return
*/
@ApiOperationSupport(author = "admin")
@ApiOperation(value = "用户登出注销token")
@GetMapping("/logout")
public R logout(){
return loginServcie.logout();
}
}
RegController注册模块
package com.demo.springSecurity.sys.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.demo.springSecurity.sys.entity.SysUserRole;
import com.demo.springSecurity.sys.entity.User;
import com.demo.springSecurity.sys.mapper.MenuMapper;
import com.demo.springSecurity.sys.mapper.SysUserRoleMapper;
import com.demo.springSecurity.sys.mapper.UserMapper;
import com.demo.springSecurity.utils.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@Api(tags = "注册模块")
@RestController
@RequestMapping("/reg")
public class RegController {
@Autowired
private UserMapper userMapper;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private MenuMapper menuMapper;
@Autowired
private SysUserRoleMapper sysUserRoleMapper;
@ApiOperationSupport(author = "admin")
@ApiOperation(value = "用户注册")
@PostMapping("/userReg")
public R reg(@RequestParam String username,@RequestParam String password){
if (username==null || password==null){
return new R(500,"用户名或者密码不能为空");
}
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUserName,username);
User user = userMapper.selectOne(wrapper);
if (user!=null){
return new R(500,"该用户已经存在");
}
String encode = passwordEncoder.encode(password);
User u = new User();
u.setUserName(username);
u.setPassword(encode);
u.setNickName("普通用户");
u.setStatus("0");
u.setDelFlag(0);
u.setCreateTime(new Date());
u.setUpdateTime(new Date());
int insert = userMapper.insert(u);
if (insert!=1){
return new R(500,"注册失败");
}
User listName = userMapper.findName(username);
if (listName!=null){
Long id = listName.getId();
SysUserRole sysUserRole = new SysUserRole();
sysUserRole.setUserId(Math.toIntExact(id));
sysUserRole.setRoleId(444); //员工默认权限为普通员工
int insert1 = sysUserRoleMapper.insert(sysUserRoleMapper);
if (insert1==1){
System.out.println("添加权限成功");
}else {
System.out.println("添加权限失败");
}
}
return new R(200,"注册成功");
}
}
TestController权限测试模块
package com.demo.springSecurity.sys.controller;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.demo.springSecurity.utils.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Api(tags = "权限认证测试")
@RestController
@RequestMapping("/oxx")
public class TestController {
@ApiOperationSupport(author = "admin")
@ApiOperation(value = "oaa权限接口才能访问")
@GetMapping("/hello")
@PreAuthorize("@ex.hasAuthority('oaa')")
// @PreAuthorize("hasAnyAuthority('admin','test','system:dept:list')")
// @PreAuthorize("hasRole('oaa')")
// @PreAuthorize("hasAnyRole('admin','system:dept:list')")
public String hello(){
return "访问成功";
}
@ApiOperationSupport(author = "admin")
@ApiOperation(value = "oaa以下权限可以访问")
@PreAuthorize("@ex.hasAuthority('obb')")
@GetMapping("/testCors")
public R testCors(){
return new R(200,"访问成功");
}
}
测试项目:http://localhost:8888/doc.html
第一步:登录获取token
第二步:配置全局token
第三步:开始测试权限
有权限
无权限的情况就换一个权限低的账号(李四 123)
SpringSecurity: 基于SpringSecurity实现的一款权限认证系统