小言_互联网的博客

SpringBoot整合SpringSecurity实现进行认证和授权。

840人阅读  评论(0)

目录

1. 设置父工程 添加依赖

2.在子工程通过easyCode创建项目相关包和文件

3.子项目新建Controllter层,并建立BlogLoginController.java

4.在servic 层定义login 方法,并new UsernamePasswordAuthenticationToken对象,传入对应用户名,密码

5.自定义实现类,实现UserDetailsService接口,重写loadUserByUsername方法,并查询数据库用户的权限信息封装传入到UserDetails。第4步中UsernamePasswordAuthenticationToken先执行到本步骤。

6.自定义一个JwtAuthenticationTokenFilter过滤器,这个过滤器会去获取每次请求当中的token,对token进行解析取出其中的userid。使用userid去redis中获取对应的LoginUser对象。然后封装Authentication对象存入SecurityContextHolder

7.我们还希望在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json,这样可以让前端能对响应进行统一的处理。要实现这个功能我们需要知道SpringSecurity的异常处理机制。

8.自定义配置类

9.授权流程,在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。

10.限制访问资源所需权限

11.其它权限校验方法

12.自定义权限校验方法

13.基于配置的权限控制

14.退出登录

15.工具类:


1. 设置父工程 添加依赖


  
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation= "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0 </modelVersion>
  6. <groupId>com.wang </groupId>
  7. <artifactId>WangBlog </artifactId>
  8. <packaging>pom </packaging>
  9. <version>1.0-SNAPSHOT </version>
  10. <modules>
  11. <module>wang-framework </module>
  12. <module>wang-admin </module>
  13. <module>wang-blog </module>
  14. </modules>
  15. <properties>
  16. <maven.compiler.source>8 </maven.compiler.source>
  17. <maven.compiler.target>8 </maven.compiler.target>
  18. <project.build.sourceEncoding>UTF-8 </project.build.sourceEncoding>
  19. </properties>
  20. <dependencyManagement>
  21. <dependencies>
  22. <!-- SpringBoot的依赖配置-->
  23. <dependency>
  24. <groupId>org.springframework.boot </groupId>
  25. <artifactId>spring-boot-dependencies </artifactId>
  26. <version>2.5.0 </version>
  27. <type>pom </type>
  28. <scope>import </scope>
  29. </dependency>
  30. <!--fastjson依赖-->
  31. <dependency>
  32. <groupId>com.alibaba </groupId>
  33. <artifactId>fastjson </artifactId>
  34. <version>1.2.33 </version>
  35. </dependency>
  36. <!--jwt依赖-->
  37. <dependency>
  38. <groupId>io.jsonwebtoken </groupId>
  39. <artifactId>jjwt </artifactId>
  40. <version>0.9.0 </version>
  41. </dependency>
  42. <!--mybatisPlus依赖-->
  43. <dependency>
  44. <groupId>com.baomidou </groupId>
  45. <artifactId>mybatis-plus-boot-starter </artifactId>
  46. <version>3.4.3 </version>
  47. </dependency>
  48. <!--阿里云OSS-->
  49. <dependency>
  50. <groupId>com.aliyun.oss </groupId>
  51. <artifactId>aliyun-sdk-oss </artifactId>
  52. <version>3.10.2 </version>
  53. </dependency>
  54. <dependency>
  55. <groupId>com.alibaba </groupId>
  56. <artifactId>easyexcel </artifactId>
  57. <version>3.0.5 </version>
  58. </dependency>
  59. <dependency>
  60. <groupId>io.springfox </groupId>
  61. <artifactId>springfox-swagger2 </artifactId>
  62. <version>2.9.2 </version>
  63. </dependency>
  64. <dependency>
  65. <groupId>io.springfox </groupId>
  66. <artifactId>springfox-swagger-ui </artifactId>
  67. <version>2.9.2 </version>
  68. </dependency>
  69. </dependencies>
  70. </dependencyManagement>
  71. <build>
  72. <plugins>
  73. <plugin>
  74. <groupId>org.apache.maven.plugins </groupId>
  75. <artifactId>maven-compiler-plugin </artifactId>
  76. <version>3.1 </version>
  77. <configuration>
  78. <source>${java.version} </source>
  79. <target>${java.version} </target>
  80. <encoding>${project.build.sourceEncoding} </encoding>
  81. </configuration>
  82. </plugin>
  83. </plugins>
  84. </build>
  85. </project>

2.在子工程通过easyCode创建项目相关包和文件

如图然后点击确定即可。 

子工程pom文件:


  
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation= "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>WangBlog </artifactId>
  7. <groupId>com.wang </groupId>
  8. <version>1.0-SNAPSHOT </version>
  9. </parent>
  10. <modelVersion>4.0.0 </modelVersion>
  11. <artifactId>wang-blog </artifactId>
  12. <properties>
  13. <maven.compiler.source>8 </maven.compiler.source>
  14. <maven.compiler.target>8 </maven.compiler.target>
  15. <project.build.sourceEncoding>UTF-8 </project.build.sourceEncoding>
  16. </properties>
  17. <dependencies>
  18. <dependency>
  19. <groupId>com.wang </groupId>
  20. <artifactId>wang-framework </artifactId>
  21. <version>1.0-SNAPSHOT </version>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.springframework.boot </groupId>
  25. <artifactId>spring-boot-autoconfigure </artifactId>
  26. <version>2.3.7.RELEASE </version>
  27. <scope>compile </scope>
  28. </dependency>
  29. </dependencies>
  30. </project>

3.子项目新建Controllter层,并建立BlogLoginController.java


  
  1. package com.wang.controller;
  2. import com.wang.entity.ResponseResult;
  3. import com.wang.entity.User;
  4. import com.wang.enums.AppHttpCodeEnum;
  5. import com.wang.exception.SystemException;
  6. import com.wang.service.BlogLoginService;
  7. import io.swagger.annotations.Api;
  8. import io.swagger.annotations.ApiOperation;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.util.StringUtils;
  11. import org.springframework.web.bind.annotation.PostMapping;
  12. import org.springframework.web.bind.annotation.RequestBody;
  13. import org.springframework.web.bind.annotation.RestController;
  14. /**
  15. * @author
  16. */
  17. @RestController
  18. @Api(tags = "登录",description = "博客登录相关接口")
  19. public class BlogLoginController {
  20. @Autowired
  21. private BlogLoginService blogLoginService;
  22. /**
  23. * @param user
  24. * @return ResponseResult
  25. * 登陆
  26. */
  27. @PostMapping("/login")
  28. @ApiOperation(value = "登陆",notes = "登陆")
  29. public ResponseResult login (@RequestBody User user){
  30. if (!StringUtils.hasText(user.getUserName())||!StringUtils.hasText(user.getPassword())){
  31. throw new SystemException(AppHttpCodeEnum.LOGIN_ERROR);
  32. }
  33. return blogLoginService.login(user);
  34. }
  35. @PostMapping("/logout")
  36. @ApiOperation(value = "退出登陆",notes = "退出登陆")
  37. public ResponseResult logout (){
  38. return blogLoginService.logout();
  39. }
  40. }

4.在servic 层定义login 方法,并new UsernamePasswordAuthenticationToken对象,传入对应用户名,密码


  
  1. @Override
  2. public ResponseResult login (User user) {
  3. UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
  4. Authentication authenticate = authenticationManager.authenticate(authenticationToken);
  5. //判断是否认证通过
  6. if(Objects.isNull(authenticate)){
  7. throw new RuntimeException( "用户名或密码错误");
  8. }
  9. //获取userid 生成token
  10. LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
  11. String userId = loginUser.getUser().getId().toString();
  12. String jwt = JwtUtil.createJWT(userId);
  13. //把用户信息存入redis
  14. redisCache.setCacheObject( "bloglogin:"+userId,loginUser);
  15. //把token和userinfo封装 返回
  16. //把User转换成UserInfoVo
  17. UserInfoVo userInfoVo = BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVo.class);
  18. BlogUserLoginVo vo = new BlogUserLoginVo(jwt,userInfoVo);
  19. return ResponseResult.okResult(vo);
  20. }

5.自定义实现类,实现UserDetailsService接口,重写loadUserByUsername方法,并查询数据库用户的权限信息封装传入到UserDetails。第4步中UsernamePasswordAuthenticationToken先执行到本步骤。


  
  1. @Service
  2. public class UserDetailServiceImpl implements UserDetailsService {
  3. @Autowired
  4. private UserMapper userMapper;
  5. @Autowired
  6. private MenuMapper menuMapper;
  7. @Override
  8. public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException {
  9. //根据用户名查询信息
  10. LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
  11. lambdaQueryWrapper.eq(User::getUserName, username);
  12. User user = userMapper.selectOne(lambdaQueryWrapper);
  13. if (user == null) {
  14. throw new RuntimeException( "用户不存在");
  15. }
  16. //返回用户信息
  17. //TODO 如果是后天需要权限封装
  18. if ( "1".equals(user.getType())){
  19. List<String> list = menuMapper.selectPermsByUserId(user.getId());
  20. return new LoginUser(user, list);
  21. }
  22. return new LoginUser(user, null);
  23. }
  24. }

loadUserByUsername返回类型为UserDetails,我们通过定义一个类实现UserDetails接口,重新其中的方法。


  
  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class LoginUser implements UserDetails {
  5. private User user;
  6. private List<String> permissions;
  7. @Override
  8. public Collection<? extends GrantedAuthority> getAuthorities() {
  9. return null;
  10. }
  11. @Override
  12. public String getPassword () {
  13. return user.getPassword();
  14. }
  15. @Override
  16. public String getUsername () {
  17. return user.getUserName();
  18. }
  19. @Override
  20. public boolean isAccountNonExpired () {
  21. return true;
  22. }
  23. @Override
  24. public boolean isAccountNonLocked () {
  25. return true;
  26. }
  27. @Override
  28. public boolean isCredentialsNonExpired () {
  29. return true;
  30. }
  31. @Override
  32. public boolean isEnabled () {
  33. return true;
  34. }
  35. }

6.自定义一个JwtAuthenticationTokenFilter过滤器,这个过滤器会去获取每次请求当中的token,对token进行解析取出其中的userid。使用userid去redis中获取对应的LoginUser对象。然后封装Authentication对象存入SecurityContextHolder


  
  1. package com.wang.filter;
  2. import com.alibaba.fastjson.JSON;
  3. import com.wang.entity.LoginUser;
  4. import com.wang.entity.ResponseResult;
  5. import com.wang.enums.AppHttpCodeEnum;
  6. import com.wang.utils.JwtUtil;
  7. import com.wang.utils.RedisCache;
  8. import com.wang.utils.WebUtils;
  9. import io.jsonwebtoken.Claims;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  12. import org.springframework.security.core.context.SecurityContextHolder;
  13. import org.springframework.stereotype.Component;
  14. import org.springframework.util.StringUtils;
  15. import org.springframework.web.filter.OncePerRequestFilter;
  16. import javax.servlet.FilterChain;
  17. import javax.servlet.ServletException;
  18. import javax.servlet.http.HttpServletRequest;
  19. import javax.servlet.http.HttpServletResponse;
  20. import java.io.IOException;
  21. import java.util.Objects;
  22. /**
  23. * @description: 自定义一个JwtAuthenticationTokenFilter过滤器,这个过滤器会去获取每次请求当中的token,对token进行解析取出其中的userid。
  24. * 使用userid去redis中获取对应的LoginUser对象。
  25. * 然后封装Authentication对象存入SecurityContextHolder
  26. * @author: wang fei
  27. * @date: 2023/1/13 19:52:24
  28. **/
  29. @Component
  30. public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
  31. @Autowired
  32. private RedisCache redisCache;
  33. @Override
  34. protected void doFilterInternal (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
  35. //获取请求头中的token
  36. String token = httpServletRequest.getHeader( "token");
  37. if (!StringUtils.hasText(token)) {
  38. //说明该接口不需要登录 直接放行
  39. filterChain.doFilter(httpServletRequest, httpServletResponse);
  40. return;
  41. }
  42. //解析userId
  43. Claims claims = null;
  44. try {
  45. claims = JwtUtil.parseJWT(token);
  46. } catch (Exception e) {
  47. e.printStackTrace();
  48. //token超时或token非法
  49. //给前端提示需要登陆
  50. ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
  51. WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
  52. return;
  53. }
  54. String userId = claims.getSubject();
  55. //从redis中获取用户信息
  56. LoginUser longinUser = redisCache.getCacheObject( "bloglogin:" + userId);
  57. //如果获取不到
  58. if (Objects.isNull(longinUser)) {
  59. //说明登陆过期 提示重新登陆
  60. ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
  61. WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
  62. return;
  63. }
  64. //存入SecurityContextHolder
  65. UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(longinUser, null, null);
  66. SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
  67. //放行
  68. filterChain.doFilter(httpServletRequest, httpServletResponse);
  69. }
  70. }

7.我们还希望在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json,这样可以让前端能对响应进行统一的处理。要实现这个功能我们需要知道SpringSecurity的异常处理机制。

在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。

如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。

​ 如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。

​ 所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPointAccessDeniedHandler然后配置给SpringSecurity即可。

自定义实现类:

AccessDeniedHandlerImpl.java


  
  1. package com.wang.handler.security;
  2. import com.alibaba.fastjson.JSON;
  3. import com.wang.entity.ResponseResult;
  4. import com.wang.enums.AppHttpCodeEnum;
  5. import com.wang.utils.WebUtils;
  6. import org.springframework.security.access.AccessDeniedException;
  7. import org.springframework.security.web.access.AccessDeniedHandler;
  8. import org.springframework.stereotype.Component;
  9. import javax.servlet.ServletException;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletResponse;
  12. import java.io.IOException;
  13. /**
  14. * @author
  15. * 如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。
  16. */
  17. @Component
  18. public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
  19. @Override
  20. public void handle (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
  21. e.printStackTrace();
  22. ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NO_OPERATOR_AUTH);
  23. //响应给前端
  24. WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
  25. }
  26. }
AuthenticationEntryPointImpl.java

  
  1. package com.wang.handler.security;
  2. import com.alibaba.fastjson.JSON;
  3. import com.wang.entity.ResponseResult;
  4. import com.wang.enums.AppHttpCodeEnum;
  5. import com.wang.utils.WebUtils;
  6. import org.springframework.security.authentication.BadCredentialsException;
  7. import org.springframework.security.authentication.InsufficientAuthenticationException;
  8. import org.springframework.security.core.AuthenticationException;
  9. import org.springframework.security.web.AuthenticationEntryPoint;
  10. import org.springframework.stereotype.Component;
  11. import javax.servlet.ServletException;
  12. import javax.servlet.http.HttpServletRequest;
  13. import javax.servlet.http.HttpServletResponse;
  14. import java.io.IOException;
  15. /**
  16. * @author
  17. * 如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。
  18. */
  19. @Component
  20. public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
  21. @Override
  22. public void commence (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
  23. e.printStackTrace();
  24. ResponseResult result= null;
  25. if (e instanceof BadCredentialsException){
  26. result=ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_ERROR.getCode(), e.getMessage());
  27. } else if (e instanceof InsufficientAuthenticationException) {
  28. result=ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_ERROR);
  29. } else {
  30. result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(), "认证或授权失败");
  31. }
  32. //响应给前端
  33. WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
  34. }
  35. }

8.自定义配置类


  
  1. package com.wang.config;
  2. import com.wang.filter.JwtAuthenticationTokenFilter;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.security.authentication.AuthenticationManager;
  7. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  8. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  9. import org.springframework.security.config.http.SessionCreationPolicy;
  10. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  11. import org.springframework.security.crypto.password.PasswordEncoder;
  12. import org.springframework.security.web.AuthenticationEntryPoint;
  13. import org.springframework.security.web.access.AccessDeniedHandler;
  14. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  15. /**
  16. * @author
  17. */
  18. @Configuration
  19. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  20. @Autowired
  21. private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
  22. @Autowired
  23. AuthenticationEntryPoint authenticationEntryPoint;
  24. @Autowired
  25. AccessDeniedHandler accessDeniedHandler;
  26. @Bean
  27. public PasswordEncoder passwordEncoder (){
  28. return new BCryptPasswordEncoder();
  29. }
  30. @Override
  31. protected void configure (HttpSecurity http) throws Exception {
  32. http
  33. //关闭csrf
  34. .csrf().disable()
  35. //不通过Session获取SecurityContext
  36. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  37. .and()
  38. .authorizeRequests()
  39. // 对于登录接口 允许匿名访问
  40. .antMatchers( "/login").anonymous()
  41. //注销接口需要认证才能访问
  42. .antMatchers( "/logout").authenticated()
  43. // .antMatchers("/upload").authenticated()
  44. //个人信息接口必须登录后才能访问
  45. .antMatchers( "/user/userInfo").authenticated()
  46. //jwt过滤器测试用,如果测试没有问题吧这里删除了
  47. // .antMatchers("/link/getAllLink").authenticated()
  48. // 除上面外的所有请求全部不需要认证即可访问
  49. .anyRequest().permitAll();
  50. //配置异常处理器
  51. http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
  52. .accessDeniedHandler(accessDeniedHandler);
  53. http.logout().disable();
  54. //关闭默认的注销功能
  55. http.logout().disable();
  56. //把jwtAuthenticationTokenFilter添加到SpringSecurity的过滤器链中
  57. http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
  58. //允许跨域
  59. http.cors();
  60. }
  61. @Override
  62. @Bean
  63. public AuthenticationManager authenticationManagerBean () throws Exception {
  64. return super.authenticationManagerBean();
  65. }
  66. }

9.授权流程,在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。

FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。

所以我们在项目中只需要把当前登录用户的权限信息也存入Authentication。 

​然后设置我们的资源所需要的权限即可。

10.限制访问资源所需权限

​SpringSecurity为我们提供了基于注解的权限控制方案,这也是我们项目中主要采用的方式。我们可以使用注解去指定访问对应的资源所需的权限。

​ 但是要使用它我们需要先开启相关配置springSecurity里面加。

@EnableGlobalMethodSecurity(prePostEnabled = true)

这里使用定义自己的权限校验方法,在@PreAuthorize注解中使用我们的方法。

例如一个接口需要对应的权限才可以访问,我们这样写:


  
  1. /**
  2. * @description: 分类导出excel @PreAuthorize("@ps.hasPermission('content:category:export')")判断是否具有content:category:export权限
  3. * @method: exportLink
  4. * @author: wang fei
  5. * @date: 2023/1/13 19:10:13
  6. * @param: [response]
  7. * @return: void
  8. **/
  9. @PreAuthorize("@ps.hasPermission('content:category:export')")
  10. @GetMapping("export")
  11. public void exportLink (HttpServletResponse response){
  12. //设置下载文件请求头
  13. try {
  14. WebUtils.setDownLoadHeader( "分类.xlsx",response);
  15. //提取需要导出的数据
  16. List<Category> list = categoryService.list();
  17. List<CategoryVo> categoryVos = BeanCopyUtils.copyBeanList(list, CategoryVo.class);
  18. //把数据写入到excel中
  19. EasyExcel.write(response.getOutputStream(), ExcelCategoryVo.class).autoCloseStream(Boolean.FALSE).sheet( "分类导出")
  20. .doWrite(categoryVos);
  21. } catch (Exception e) {
  22. //如果出现异常也要响应json
  23. ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR);
  24. WebUtils.renderString(response, JSON.toJSONString(result));
  25. }
  26. }

@PreAuthorize("@ps.hasPermission('content:category:export')"):

ps为我们定义的bean,hasPermission为我们定义的权限判断方法返回类型为bool,content:category:export为权限信息。


  
  1. package com.wang.service.impl;
  2. import com.wang.utils.SecurityUtils;
  3. import org.springframework.stereotype.Service;
  4. import java.util.List;
  5. /**
  6. * @BelongsProject: WangBlog
  7. * @BelongsPackage: com.wang.service.impl
  8. * @Author: wang fei
  9. * @CreateTime: 2023-01-13 19:04
  10. * @Description: TODO
  11. * @Version: 1.0
  12. */
  13. @Service("ps")
  14. public class PermissionService {
  15. /**
  16. * @description: 判断当前用户是否具有权限
  17. * @method: hasPermission
  18. * @author: wang fei
  19. * @date: 2023/1/13 19:05:47
  20. * @param: [permission]
  21. * @return: boolean
  22. **/
  23. public boolean hasPermission (String permission){
  24. //如果是超级管理员 直返回true
  25. if (SecurityUtils.isAdmin()) {
  26. return true;
  27. }
  28. //否则 获取当前用户的权限列表 判断是否存在权限
  29. List<String> permissions = SecurityUtils.getLoginUser().getPermissions();
  30. return permissions.contains(permission);
  31. }
  32. }

11.其它权限校验方法

​ 我们前面都是使用@PreAuthorize注解,然后在在其中使用的是hasAuthority方法进行校验。SpringSecurity还为我们提供了其它方法例如:hasAnyAuthorityhasRolehasAnyRole等。

​ 这里我们先不急着去介绍这些方法,我们先去理解hasAuthority的原理,然后再去学习其他方法你就更容易理解,而不是死记硬背区别。并且我们也可以选择定义校验方法,实现我们自己的校验逻辑。

hasAuthority方法实际是执行到了SecurityExpressionRoothasAuthority,大家只要断点调试既可知道它内部的校验原理。

​ 它内部其实是调用authenticationgetAuthorities方法获取用户的权限列表。然后判断我们存入的方法参数数据在权限列表中。

hasAnyAuthority方法可以传入多个权限,只有用户有其中任意一个权限都可以访问对应资源。


  
  1. @PreAuthorize("hasAnyAuthority('admin','test','system:dept:list')")
  2. public String hello (){
  3. return "hello";
  4. }

hasRole要求有对应的角色才可以访问,但是它内部会把我们传入的参数拼接上 ROLE_ 后再去比较。所以这种情况下要用用户对应的权限也要有 ROLE_ 这个前缀才可以。


  
  1. @PreAuthorize("hasRole('system:dept:list')")
  2. public String hello (){
  3. return "hello";
  4. }

hasAnyRole 有任意的角色就可以访问。它内部也会把我们传入的参数拼接上 ROLE_ 后再去比较。所以这种情况下要用用户对应的权限也要有 ROLE_ 这个前缀才可以。


  
  1. @PreAuthorize("hasAnyRole('admin','system:dept:list')")
  2. public String hello (){
  3. return "hello";
  4. }

12.自定义权限校验方法

​ 我们也可以定义自己的权限校验方法,在@PreAuthorize注解中使用我们的方法。


  
  1. @Component("ex")
  2. public class SGExpressionRoot {
  3. public boolean hasAuthority (String authority){
  4. //获取当前用户的权限
  5. Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
  6. LoginUser loginUser = (LoginUser) authentication.getPrincipal();
  7. List<String> permissions = loginUser.getPermissions();
  8. //判断用户权限集合中是否存在authority
  9. return permissions.contains(authority);
  10. }
  11. }

在SPEL表达式中使用 @ex相当于获取容器中bean的名字未ex的对象。然后再调用这个对象的hasAuthority方法


  
  1. @RequestMapping("/hello")
  2. @PreAuthorize("@ex.hasAuthority('system:dept:list')")
  3. public String hello (){
  4. return "hello";
  5. }

13.基于配置的权限控制

​ 我们也可以在配置类中使用使用配置的方式对资源进行权限控制。


  
  1. @Override
  2. protected void configure (HttpSecurity http) throws Exception {
  3. http
  4. //关闭csrf
  5. .csrf().disable()
  6. //不通过Session获取SecurityContext
  7. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  8. .and()
  9. .authorizeRequests()
  10. // 对于登录接口 允许匿名访问
  11. .antMatchers( "/user/login").anonymous()
  12. .antMatchers( "/testCors").hasAuthority( "system:dept:list222")
  13. // 除上面外的所有请求全部需要鉴权认证
  14. .anyRequest().authenticated();
  15. //添加过滤器
  16. http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
  17. //配置异常处理器
  18. http.exceptionHandling()
  19. //配置认证失败处理器
  20. .authenticationEntryPoint(authenticationEntryPoint)
  21. .accessDeniedHandler(accessDeniedHandler);
  22. //允许跨域
  23. http.cors();
  24. }

14.退出登录


  
  1. public ResponseResult logout () {
  2. //获取token 解析获取userid
  3. Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
  4. LoginUser logUser = (LoginUser) authentication.getPrincipal();
  5. //获取userid
  6. Long userId = logUser.getUser().getId();
  7. //删除redis中的用户信息
  8. redisCache.deleteObject( "bloglogin:"+userId);
  9. return ResponseResult.okResult();
  10. }

15.工具类:

BeanCopyUtils.java

  
  1. package com.wang.utils;
  2. import org.springframework.beans.BeanUtils;
  3. import java.util.List;
  4. import java.util.stream.Collectors;
  5. /**
  6. * @author
  7. */
  8. public class BeanCopyUtils {
  9. private BeanCopyUtils () {
  10. }
  11. public static <V> V copyBean (Object source, Class<V> clazz){
  12. //创建目标对象
  13. V result = null;
  14. try {
  15. //反射
  16. result = clazz.newInstance();
  17. //实现属性copy
  18. BeanUtils.copyProperties(source, result);
  19. } catch (InstantiationException | IllegalAccessException e) {
  20. throw new RuntimeException(e);
  21. }
  22. //返回结果
  23. return result;
  24. }
  25. public static <O,V> List<V> copyBeanList (List<O> list, Class<V> clazz){
  26. return list.stream().map(o->copyBean(o, clazz)).collect(Collectors.toList());
  27. }
  28. }

JwtUtil .java


  
  1. package com.wang.utils;
  2. import io.jsonwebtoken.Claims;
  3. import io.jsonwebtoken.JwtBuilder;
  4. import io.jsonwebtoken.Jwts;
  5. import io.jsonwebtoken.SignatureAlgorithm;
  6. import javax.crypto.SecretKey;
  7. import javax.crypto.spec.SecretKeySpec;
  8. import java.util.Base64;
  9. import java.util.Date;
  10. import java.util.UUID;
  11. /**
  12. * JWT工具类
  13. * @author
  14. */
  15. public class JwtUtil {
  16. //有效期为
  17. public static final Long JWT_TTL = 24* 60 * 60 * 1000L; // 60 * 60 *1000 一个小时
  18. //设置秘钥明文
  19. public static final String JWT_KEY = "wangfei";
  20. public static String getUUID (){
  21. String token = UUID.randomUUID().toString().replaceAll( "-", "");
  22. return token;
  23. }
  24. /**
  25. * 生成jtw
  26. * @param subject token中要存放的数据(json格式)
  27. * @return
  28. */
  29. public static String createJWT (String subject) {
  30. JwtBuilder builder = getJwtBuilder(subject, null, getUUID()); // 设置过期时间
  31. return builder.compact();
  32. }
  33. /**
  34. * 生成jtw
  35. * @param subject token中要存放的数据(json格式)
  36. * @param ttlMillis token超时时间
  37. * @return
  38. */
  39. public static String createJWT (String subject, Long ttlMillis) {
  40. JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID()); // 设置过期时间
  41. return builder.compact();
  42. }
  43. private static JwtBuilder getJwtBuilder (String subject, Long ttlMillis, String uuid) {
  44. SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
  45. SecretKey secretKey = generalKey();
  46. long nowMillis = System.currentTimeMillis();
  47. Date now = new Date(nowMillis);
  48. if(ttlMillis== null){
  49. ttlMillis=JwtUtil.JWT_TTL;
  50. }
  51. long expMillis = nowMillis + ttlMillis;
  52. Date expDate = new Date(expMillis);
  53. return Jwts.builder()
  54. .setId(uuid) //唯一的ID
  55. .setSubject(subject) // 主题 可以是JSON数据
  56. .setIssuer( "sg") // 签发者
  57. .setIssuedAt(now) // 签发时间
  58. .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
  59. .setExpiration(expDate);
  60. }
  61. /**
  62. * 创建token
  63. * @param id
  64. * @param subject
  65. * @param ttlMillis
  66. * @return
  67. */
  68. public static String createJWT (String id, String subject, Long ttlMillis) {
  69. JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id); // 设置过期时间
  70. return builder.compact();
  71. }
  72. public static void main (String[] args) throws Exception {
  73. String token = "";
  74. Claims claims = parseJWT(token);
  75. System.out.println(claims);
  76. }
  77. /**
  78. * 生成加密后的秘钥 secretKey
  79. * @return
  80. */
  81. public static SecretKey generalKey () {
  82. byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
  83. SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
  84. return key;
  85. }
  86. /**
  87. * 解析
  88. *
  89. * @param jwt
  90. * @return
  91. * @throws Exception
  92. */
  93. public static Claims parseJWT (String jwt) throws Exception {
  94. SecretKey secretKey = generalKey();
  95. return Jwts.parser()
  96. .setSigningKey(secretKey)
  97. .parseClaimsJws(jwt)
  98. .getBody();
  99. }
  100. }
PathUtils.java

  
  1. package com.wang.utils;
  2. import java.text.SimpleDateFormat;
  3. import java.util.Date;
  4. import java.util.UUID;
  5. /**
  6. * @author
  7. */
  8. public class PathUtils {
  9. public static String generateFilePath (String fileName){
  10. //根据日期生成路径 2022/1/15/
  11. SimpleDateFormat sdf = new SimpleDateFormat( "yyyy/MM/dd/");
  12. String datePath = sdf.format( new Date());
  13. //uuid作为文件名
  14. String uuid = UUID.randomUUID().toString().replaceAll( "-", "");
  15. //后缀和文件后缀一致
  16. int index = fileName.lastIndexOf( ".");
  17. // test.jpg -> .jpg
  18. String fileType = fileName.substring(index);
  19. return new StringBuilder().append(datePath).append(uuid).append(fileType).toString();
  20. }
  21. }
RedisCache.java

  
  1. package com.wang.utils;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.data.redis.core.BoundSetOperations;
  4. import org.springframework.data.redis.core.HashOperations;
  5. import org.springframework.data.redis.core.RedisTemplate;
  6. import org.springframework.data.redis.core.ValueOperations;
  7. import org.springframework.stereotype.Component;
  8. import java.util.*;
  9. import java.util.concurrent.TimeUnit;
  10. /**
  11. * @author
  12. */
  13. @Component
  14. public class RedisCache
  15. {
  16. @Autowired
  17. public RedisTemplate redisTemplate;
  18. /**
  19. * 缓存基本的对象,Integer、String、实体类等
  20. *
  21. * @param key 缓存的键值
  22. * @param value 缓存的值
  23. */
  24. public <T> void setCacheObject (final String key, final T value)
  25. {
  26. redisTemplate.opsForValue().set(key, value);
  27. }
  28. /**
  29. * 缓存基本的对象,Integer、String、实体类等
  30. *
  31. * @param key 缓存的键值
  32. * @param value 缓存的值
  33. * @param timeout 时间
  34. * @param timeUnit 时间颗粒度
  35. */
  36. public <T> void setCacheObject (final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
  37. {
  38. redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
  39. }
  40. /**
  41. * 设置有效时间
  42. *
  43. * @param key Redis键
  44. * @param timeout 超时时间
  45. * @return true=设置成功;false=设置失败
  46. */
  47. public boolean expire (final String key, final long timeout)
  48. {
  49. return expire(key, timeout, TimeUnit.SECONDS);
  50. }
  51. /**
  52. * 设置有效时间
  53. *
  54. * @param key Redis键
  55. * @param timeout 超时时间
  56. * @param unit 时间单位
  57. * @return true=设置成功;false=设置失败
  58. */
  59. public boolean expire (final String key, final long timeout, final TimeUnit unit)
  60. {
  61. return redisTemplate.expire(key, timeout, unit);
  62. }
  63. /**
  64. * 获得缓存的基本对象。
  65. *
  66. * @param key 缓存键值
  67. * @return 缓存键值对应的数据
  68. */
  69. public <T> T getCacheObject (final String key)
  70. {
  71. ValueOperations<String, T> operation = redisTemplate.opsForValue();
  72. return operation.get(key);
  73. }
  74. /**
  75. * 删除单个对象
  76. *
  77. * @param key
  78. */
  79. public boolean deleteObject (final String key)
  80. {
  81. return redisTemplate.delete(key);
  82. }
  83. /**
  84. * 删除集合对象
  85. *
  86. * @param collection 多个对象
  87. * @return
  88. */
  89. public long deleteObject (final Collection collection)
  90. {
  91. return redisTemplate.delete(collection);
  92. }
  93. /**
  94. * 缓存List数据
  95. *
  96. * @param key 缓存的键值
  97. * @param dataList 待缓存的List数据
  98. * @return 缓存的对象
  99. */
  100. public <T> long setCacheList (final String key, final List<T> dataList)
  101. {
  102. Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
  103. return count == null ? 0 : count;
  104. }
  105. /**
  106. * 获得缓存的list对象
  107. *
  108. * @param key 缓存的键值
  109. * @return 缓存键值对应的数据
  110. */
  111. public <T> List<T> getCacheList (final String key)
  112. {
  113. return redisTemplate.opsForList().range(key, 0, - 1);
  114. }
  115. /**
  116. * 缓存Set
  117. *
  118. * @param key 缓存键值
  119. * @param dataSet 缓存的数据
  120. * @return 缓存数据的对象
  121. */
  122. public <T> BoundSetOperations<String, T> setCacheSet (final String key, final Set<T> dataSet)
  123. {
  124. BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
  125. Iterator<T> it = dataSet.iterator();
  126. while (it.hasNext())
  127. {
  128. setOperation.add(it.next());
  129. }
  130. return setOperation;
  131. }
  132. /**
  133. * 获得缓存的set
  134. *
  135. * @param key
  136. * @return
  137. */
  138. public <T> Set<T> getCacheSet (final String key)
  139. {
  140. return redisTemplate.opsForSet().members(key);
  141. }
  142. /**
  143. * 缓存Map
  144. *
  145. * @param key
  146. * @param dataMap
  147. */
  148. public <T> void setCacheMap (final String key, final Map<String, T> dataMap)
  149. {
  150. if (dataMap != null) {
  151. redisTemplate.opsForHash().putAll(key, dataMap);
  152. }
  153. }
  154. /**
  155. * 获得缓存的Map
  156. *
  157. * @param key
  158. * @return
  159. */
  160. public <T> Map<String, T> getCacheMap (final String key)
  161. {
  162. return redisTemplate.opsForHash().entries(key);
  163. }
  164. /**
  165. * 往Hash中存入数据
  166. *
  167. * @param key Redis键
  168. * @param hKey Hash键
  169. * @param value 值
  170. */
  171. public <T> void setCacheMapValue (final String key, final String hKey, final T value)
  172. {
  173. redisTemplate.opsForHash().put(key, hKey, value);
  174. }
  175. /**
  176. * 获取Hash中的数据
  177. *
  178. * @param key Redis键
  179. * @param hKey Hash键
  180. * @return Hash中的对象
  181. */
  182. public <T> T getCacheMapValue (final String key, final String hKey)
  183. {
  184. HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
  185. return opsForHash.get(key, hKey);
  186. }
  187. /**
  188. * 删除Hash中的数据
  189. *
  190. * @param key
  191. * @param hkey
  192. */
  193. public void delCacheMapValue (final String key, final String hkey)
  194. {
  195. HashOperations hashOperations = redisTemplate.opsForHash();
  196. hashOperations.delete(key, hkey);
  197. }
  198. /**
  199. * 获取多个Hash中的数据
  200. *
  201. * @param key Redis键
  202. * @param hKeys Hash键集合
  203. * @return Hash对象集合
  204. */
  205. public <T> List<T> getMultiCacheMapValue (final String key, final Collection<Object> hKeys)
  206. {
  207. return redisTemplate.opsForHash().multiGet(key, hKeys);
  208. }
  209. /**
  210. * 获得缓存的基本对象列表
  211. *
  212. * @param pattern 字符串前缀
  213. * @return 对象列表
  214. */
  215. public Collection<String> keys (final String pattern)
  216. {
  217. return redisTemplate.keys(pattern);
  218. }
  219. public void incrementCacheMapValue (String key,String hKey,long v){
  220. redisTemplate.boundHashOps(key).increment(hKey, v);
  221. }
  222. }
SecurityUtils.java

  
  1. package com.wang.utils;
  2. import com.wang.entity.LoginUser;
  3. import org.springframework.security.core.Authentication;
  4. import org.springframework.security.core.context.SecurityContextHolder;
  5. /**
  6. * @author
  7. */
  8. public class SecurityUtils
  9. {
  10. /**
  11. * 获取用户
  12. **/
  13. public static LoginUser getLoginUser ()
  14. {
  15. return (LoginUser) getAuthentication().getPrincipal();
  16. }
  17. /**
  18. * 获取Authentication
  19. */
  20. public static Authentication getAuthentication () {
  21. return SecurityContextHolder.getContext().getAuthentication();
  22. }
  23. public static Boolean isAdmin (){
  24. Long id = getLoginUser().getUser().getId();
  25. return id != null && 1L == id;
  26. }
  27. public static Long getUserId () {
  28. return getLoginUser().getUser().getId();
  29. }
  30. }

WebUtils.java


  
  1. package com.wang.utils;
  2. import org.springframework.web.context.request.RequestContextHolder;
  3. import javax.servlet.ServletContext;
  4. import javax.servlet.http.HttpServletResponse;
  5. import java.io.IOException;
  6. import java.io.UnsupportedEncodingException;
  7. import java.net.URLEncoder;
  8. /**
  9. * @author
  10. */
  11. public class WebUtils
  12. {
  13. /**
  14. * 将字符串渲染到客户端
  15. *
  16. * @param response 渲染对象
  17. * @param string 待渲染的字符串
  18. * @return null
  19. */
  20. public static void renderString (HttpServletResponse response, String string) {
  21. try
  22. {
  23. response.setStatus( 200);
  24. response.setContentType( "application/json");
  25. response.setCharacterEncoding( "utf-8");
  26. response.getWriter().print(string);
  27. }
  28. catch (IOException e)
  29. {
  30. e.printStackTrace();
  31. }
  32. }
  33. public static void setDownLoadHeader (String filename, HttpServletResponse response) throws UnsupportedEncodingException {
  34. response.setContentType( "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
  35. response.setCharacterEncoding( "utf-8");
  36. String fname= URLEncoder.encode(filename, "UTF-8").replaceAll( "\\+", "%20");
  37. response.setHeader( "Content-disposition", "attachment; filename="+fname);
  38. }
  39. }


转载:https://blog.csdn.net/qq_51438138/article/details/128678530
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场