笔记
1.2 原理初探
1.2.1 SpringSecurity完整流程
SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。
图中只展示了核心过滤器,其它的非核心过滤器并没有在图中展示。
UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。
ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException 。
FilterSecurityInterceptor:负责权限校验的过滤器。
1.2.2 认证流程详解
概念速查:
Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。
AuthenticationManager接口:定义了认证Authentication的方法
UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。
UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。
登录
①自定义登录接口
调用ProviderManager的方法进行认证 如果认证通过生成jwt
把用户信息存入redis中
②自定义UserDetailsService
在这个实现类中去查询数据库
校验:
①定义Jwt认证过滤器
获取token
解析token获取其中的userid
从redis中获取用户信息
存入SecurityContextHolder
2. 授权
在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。
所以在项目中只需要把当前登录用户的权限信息也存入Authentication。
然后设置资源所需要的权限即可。
2.2 授权实现
2.2.1 限制访问资源所需权限
SpringSecurity提供了基于注解的权限控制方案,这也是项目中主要采用的方式。我们可以使用注解去指定访问对应的资源所需的权限。
2.2.2 封装权限信息
在查询出用户后还要获取对应的权限信息,封装到UserDetails中返回。
2.2.3 从数据库查询权限信息(RBAC权限模型)
RBAC权限模型(Role-Based Access Control)即:基于角色的权限控制。这是目前最常被开发者使用也是相对易用、通用权限模型。
代码
0. 目录
1. 其他
SecurityDemo1Application

package com.bijian.security_demo1; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.bijian.security_demo1.mapper") public class SecurityDemo1Application { public static void main(String[] args) { SpringApplication.run(SecurityDemo1Application.class, args); } }
application.yml

spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/springsecurity?serverTimezone=UTC username: root password: 123456 #redis redis: timeout: 10000ms host: localhost port: 6379 database: 0 lettuce: pool: max-active: 1024 max-wait: 10000ms max-idle: 200 min-idle: 5 mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml server: port: 8888
pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.6</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.bijian</groupId> <artifactId>security_demo1</artifactId> <version>0.0.1-SNAPSHOT</version> <name>security_demo1</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--SpringSecurity引入--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!--redis依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--fastjson依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.33</version> </dependency> <!--jwt依赖--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <!--引入MybatisPuls和mysql驱动的依赖--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.0</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--redis依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--commons-pool2依赖--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
2. config
CorsConfig

package com.bijian.security_demo1.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); } }
RedisConfig

package com.bijian.security_demo1.config; import com.bijian.security_demo1.utils.FastJsonRedisSerializer; 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; @Configuration public class RedisConfig { @Bean @SuppressWarnings(value = { "unchecked", "rawtypes" }) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class); // 使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); // Hash的key也采用StringRedisSerializer的序列化方式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } }
SecurityConfig
package com.bijian.security_demo1.config; import com.bijian.security_demo1.filter.JwtAuthenticationTokenFilter; import com.bijian.security_demo1.handler.AccessDeniedHandlerImpl; import com.bijian.security_demo1.handler.AuthenticationEntryPointImpl; 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 JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; @Autowired private AuthenticationEntryPoint authenticationEntryPoint; @Autowired private AccessDeniedHandler accessDeniedHandler; @Bean // 创建BCryptPasswordEncoder注入容器 public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { http //关闭csrf .csrf().disable() //不通过Session获取SecurityContext .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 对于登录接口 允许匿名访问 .antMatchers("/user/login").anonymous() .antMatchers("/testCors").hasAnyAuthority("system:dept:list") // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated(); // 添加过滤器 http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // 配置异常处理器 http.exceptionHandling() //配置认证失败处理 .authenticationEntryPoint(authenticationEntryPoint) //配置授权失败处理 .accessDeniedHandler(accessDeniedHandler); // 允许跨域 http.cors(); } }
3. controller
HelloController
package com.bijian.security_demo1.controller; import com.bijian.security_demo1.demain.ResponseResult; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @RequestMapping("/hello") @PreAuthorize("hasAuthority('system:dept:list111')") // @PreAuthorize("@ex.hasAuthority('system:dept:list')") public String hello(){ return "hello"; } @RequestMapping("/testCors") public ResponseResult testCors(){ return new ResponseResult(200,"testCors"); } }
LoginController
package com.bijian.security_demo1.controller; import com.bijian.security_demo1.demain.ResponseResult; import com.bijian.security_demo1.demain.User; import com.bijian.security_demo1.service.LoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class LoginController { @Autowired private LoginService loginService; @PostMapping("/user/login") public ResponseResult login(@RequestBody User user){ return loginService.login(user); } @RequestMapping("/user/logout") public ResponseResult logout(){ return loginService.logout(); } }
4. demain
LoginUser

package com.bijian.security_demo1.demain; import com.alibaba.fastjson.annotation.JSONField; import lombok.AllArgsConstructor; 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; import java.util.stream.Collectors; @Data @NoArgsConstructor //@AllArgsConstructor public class LoginUser implements UserDetails { private User user; public LoginUser(User user, List<String> permissions) { this.user = user; this.permissions = permissions; } //存储权限信息 private List<String> permissions; @JSONField(serialize = false) private List<SimpleGrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { if (authorities!=null){ return authorities; } //把permissions中字符串类型的权限信息转换成GrantedAuthority对象存入authorities中 // 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.bijian.security_demo1.demain; 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; }
ResponseResult

package com.bijian.security_demo1.demain; import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_NULL) public class ResponseResult<T> { /** * 状态码 */ private Integer code; /** * 提示信息,如果有错误时,前端可以获取该字段进行提示 */ private String msg; /** * 查询到的结果数据, */ private T data; public ResponseResult(Integer code, String msg) { this.code = code; this.msg = msg; } public ResponseResult(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 ResponseResult(Integer code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } }
User

package com.bijian.security_demo1.demain; 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(value = "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; }
5. expression
BJExpressionRoot
package com.bijian.security_demo1.expression; import com.bijian.security_demo1.demain.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 BJExpressionRoot { public boolean hasAuthority(String authority){ //获取当前用户的权限 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); List<String> permissions = loginUser.getPermissions(); //判断用户权限集合中是否存在authority return permissions.contains(authority); } }
6. filter
JwtAuthenticationTokenFilter
package com.bijian.security_demo1.filter; import com.bijian.security_demo1.demain.LoginUser; import com.bijian.security_demo1.utils.JwtUtil; import com.bijian.security_demo1.utils.RedisCache; 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; /** * 获取请求头中的token,对token进行解析取出其中的userid * 使用userid去redis中获取对应的LoginUser对象 * 然后封装Authentication对象存入SecurityContextHolder */ @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; } String userid; //解析token try { Claims claims = JwtUtil.parseJWT(token); userid = claims.getSubject(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("token非法"); } //从redis中获取用户信息 String redisKey = "login"+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); } }
7. handler
AccessDeniedHandlerImpl
package com.bijian.security_demo1.handler; import com.alibaba.fastjson.JSON; import com.bijian.security_demo1.demain.ResponseResult; import com.bijian.security_demo1.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 httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(), "您的权限不足"); String json = JSON.toJSONString(result); //处理异常 WebUtils.renderString(httpServletResponse,json); } }
AuthenticationEntryPointImpl
package com.bijian.security_demo1.handler; import com.alibaba.fastjson.JSON; import com.bijian.security_demo1.demain.ResponseResult; import com.bijian.security_demo1.utils.WebUtils; 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 public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "用户认证失败请查询登录"); String json = JSON.toJSONString(result); //处理异常 WebUtils.renderString(httpServletResponse,json); } }
8. java/mapper
MenuMapper
package com.bijian.security_demo1.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.bijian.security_demo1.demain.Menu; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface MenuMapper extends BaseMapper<Menu> { List<String> selectPermsByUserId(Long id); }
UserMapper
package com.bijian.security_demo1.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.bijian.security_demo1.demain.User; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface UserMapper extends BaseMapper<User> { // 根据用户id去查询到其所对应的权限信息 List<String> selectPermsByUserId(Long id); }
9. service
LoginService
package com.bijian.security_demo1.service; import com.bijian.security_demo1.demain.ResponseResult; import com.bijian.security_demo1.demain.User; public interface LoginService { ResponseResult login(User user); ResponseResult logout(); }
LoginServiceImpl
package com.bijian.security_demo1.service.impl; import com.bijian.security_demo1.demain.LoginUser; import com.bijian.security_demo1.demain.ResponseResult; import com.bijian.security_demo1.demain.User; import com.bijian.security_demo1.service.LoginService; import com.bijian.security_demo1.utils.JwtUtil; import com.bijian.security_demo1.utils.RedisCache; 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.stereotype.Service; import java.util.HashMap; import java.util.Map; import java.util.Objects; @Service public class LoginServiceImpl implements LoginService { @Autowired private AuthenticationManager authenticationManager; @Autowired private RedisCache redisCache; @Override public ResponseResult login(User user) { // AuthenticationManager authenticate 进行用户认证 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword()); 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<String,String> map = new HashMap<>(); map.put("token",jwt); //把完整的用户信息存入Redis userid作为key redisCache.setCacheObject("login"+userid,loginUser); return new ResponseResult(200,"登录成功",map); } @Override // 需要定义一个登陆接口,然后获取SecurityContextHolder中的认证信息,删除redis中对应的数据即可 public ResponseResult logout() { // 获取SecurityContextHolder中的用户id Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); Long userid = loginUser.getUser().getId(); // 删除Redis中的值 redisCache.deleteObject("login" + userid); return new ResponseResult(200,"退出成功"); } }
UserDetailsServiceImpl
package com.bijian.security_demo1.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.bijian.security_demo1.demain.LoginUser; import com.bijian.security_demo1.demain.User; import com.bijian.security_demo1.mapper.MenuMapper; import com.bijian.security_demo1.mapper.UserMapper; 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.ArrayList; import java.util.Arrays; 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 { //根据用户名查询用户信息 User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserName, username)); if(Objects.isNull(user)){ throw new RuntimeException("用户名或密码错误"); } //TODO 根据用户查询权限信息 添加到LoginUser中 // List<String> list = new ArrayList<>(Arrays.asList("test","admin")); List<String> list = menuMapper.selectPermsByUserId(user.getId()); return new LoginUser(user,list); } }
10. utils
FastJsonRedisSerializer

package com.bijian.security_demo1.utils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import com.alibaba.fastjson.parser.ParserConfig; import org.springframework.util.Assert; import java.nio.charset.Charset; /** * Redis使用FastJson序列化 * * @author sg */ public class FastJsonRedisSerializer<T> implements RedisSerializer<T> { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private Class<T> clazz; static { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); } public FastJsonRedisSerializer(Class<T> 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); } }
JwtUtil

package com.bijian.security_demo1.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 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 = "sangeng"; 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("sg") // 签发者 .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 token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjMmRkMmRmNDJkYjQ0N2JjYjQ1NjNkMDYxNzk3OTE2NiIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTY4OTkzMDQ0OCwiZXhwIjoxNjg5OTM0MDQ4fQ.j-pP17z0KWvOPCqAxZWHE_Ltsx-lIydo4hwgF43B7KY"; // Claims claims = parseJWT(token); // System.out.println(claims); // System.out.println(claims.getSubject()); BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); // String encode = passwordEncoder.encode("123456"); String encode = passwordEncoder.encode("654321"); System.out.println(encode); } /** * 生成加密后的秘钥 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(); } }
RedisCache

package com.bijian.security_demo1.utils; 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; @Component @SuppressWarnings(value = { "unchecked", "rawtypes" }) public class RedisCache { @Autowired public RedisTemplate redisTemplate; /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 */ public <T> void setCacheObject(final String key, final T value) { redisTemplate.opsForValue().set(key, value); } /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 */ public <T> 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> T getCacheObject(final String key) { ValueOperations<String, T> 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 <T> long setCacheList(final String key, final List<T> dataList) { Long count = redisTemplate.opsForList().rightPushAll(key, dataList); return count == null ? 0 : count; } /** * 获得缓存的list对象 * * @param key 缓存的键值 * @return 缓存键值对应的数据 */ public <T> List<T> getCacheList(final String key) { return redisTemplate.opsForList().range(key, 0, -1); } /** * 缓存Set * * @param key 缓存键值 * @param dataSet 缓存的数据 * @return 缓存数据的对象 */ public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) { BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key); Iterator<T> it = dataSet.iterator(); while (it.hasNext()) { setOperation.add(it.next()); } return setOperation; } /** * 获得缓存的set * * @param key * @return */ public <T> Set<T> getCacheSet(final String key) { return redisTemplate.opsForSet().members(key); } /** * 缓存Map * * @param key * @param dataMap */ public <T> void setCacheMap(final String key, final Map<String, T> dataMap) { if (dataMap != null) { redisTemplate.opsForHash().putAll(key, dataMap); } } /** * 获得缓存的Map * * @param key * @return */ public <T> Map<String, T> getCacheMap(final String key) { return redisTemplate.opsForHash().entries(key); } /** * 往Hash中存入数据 * * @param key Redis键 * @param hKey Hash键 * @param value 值 */ public <T> 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> T getCacheMapValue(final String key, final String hKey) { HashOperations<String, String, T> 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 <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) { return redisTemplate.opsForHash().multiGet(key, hKeys); } /** * 获得缓存的基本对象列表 * * @param pattern 字符串前缀 * @return 对象列表 */ public Collection<String> keys(final String pattern) { return redisTemplate.keys(pattern); } }
WebUtils

package com.bijian.security_demo1.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; } }
11.resources/mapper
MenuMapper
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.bijian.security_demo1.mapper.MenuMapper"> <select id="selectPermsByUserId" resultType="java.lang.String"> SELECT DISTINCT m.`perms` FROM sys_user_role ur LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id` LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id` LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id` WHERE user_id = #{userid} AND r.`status` = 0 AND m.`status` = 0 </select> </mapper>
12.test
MapperTest
package com.bijian.security_demo1; import com.bijian.security_demo1.demain.User; import com.bijian.security_demo1.mapper.MenuMapper; import com.bijian.security_demo1.mapper.UserMapper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import java.util.List; @SpringBootTest public class MapperTest { @Autowired private UserMapper userMapper; @Autowired private MenuMapper menuMapper; @Autowired private PasswordEncoder passwordEncoder; @Test public void testUserMapper(){ List<User> users = userMapper.selectList(null); System.out.println(users); } @Test public void TestBCryptPasswordEncoder(){ // $2a$10$VdOdYCdkS6eszRsodoLe6OY.Y/wCPzGAaC0.m8zOhhhO2lHTq9Y2a BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String encode = passwordEncoder.encode("123456"); // String encode2 = passwordEncoder.encode("123456"); System.out.println(encode); // System.out.println(encode2); // boolean result = passwordEncoder.matches("123456", "$2a$10$VdOdYCdkS6eszRsodoLe6OY.Y/wCPzGAaC0.m8zOhhhO2lHTq9Y2a"); // System.out.println(result); } @Test public void testSelectPermsByUserId(){ List<String> list = menuMapper.selectPermsByUserId(1L); System.out.println(list); } }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?