筆記

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); } }

浙公網安備 33010602011771號