• SpringBoot整合SpringSecurity


    什么是SpringSecurity?

    Spring Security是Spring提供的一套web的应用安全性的完整解决方案。

    SpringSecurity采用责任式链的设计模式,它的核心是一组过滤器链。

    主要包括:

    • 认证(Authentication):什么是认证?简单的说就是检验某个用户是否为系统合法用户,需要用户提供用户名和密码进行校验当前用户能否访问系统。
    • 授权(Authorization):就是当前用户?是否具有某种权限来进行某种操作。

    SpringSecurity的认证流程

    1. 用户在浏览器中访问一个需要认证的URL。
    2. Spring Security会检查用户是否已经被认证(即是否已登录)。如果用户已经认证,那么他们可以正常访问该URL。如果用户未被认证,那么他们将被重定向到loginPage()对应的URL,通常是登录页面。
    3. 用户在登录页面输入用户名和密码,然后点击登录按钮,发起登录请求。
    4. 如果请求的URL和loginProcessingUrl()一致,那么Spring Security将开始执行登录流程。否则,用户可能需要重新进行认证。
    5. 在执行登录流程时,首先会经过UsernamePasswordAuthenticationFilter过滤器,该过滤器会取出用户输入的用户名和密码,然后将其放入一个容器(UsernamePasswordAuthenticationToken)中。这个容器会被用于后续的认证流程。
    6. 接下来,UsernamePasswordAuthenticationToken会被提交给AuthenticationManager进行管理。AuthenticationManager会委托给AuthenticationProvider进行具体的认证操作。在这个过程中,可能会涉及到密码的加密和比对。
    7. 如果认证成功,那么用户的认证信息将被存储在session中,并且用户可以正常访问他们之前试图访问的URL。如果认证失败,那么用户可能会被重定向到失败URL(如果配置了的话),或者可能会看到一个错误页面

    代码示例

    简单案例

    1,引入maven依赖

    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-security</artifactId>
    4. </dependency>

    2,启动服务

    3,访问 http://localhost:8080/ 会自动跳转进入登录页面 默认用户是 user  密码为服务后端启动显示的安全密码

    4,登录成功访问我们提供的接口

    5,退出登录 访问 http://localhost:8080/logout

    完整案例

    1,引入maven依赖

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-securityartifactId>
    4. dependency>

    2,User类

    1. package com.security.domain;
    2. import com.baomidou.mybatisplus.annotation.IdType;
    3. import com.baomidou.mybatisplus.annotation.TableId;
    4. import com.baomidou.mybatisplus.annotation.TableName;
    5. import io.swagger.annotations.ApiModelProperty;
    6. import lombok.AllArgsConstructor;
    7. import lombok.Data;
    8. import lombok.NoArgsConstructor;
    9. import java.io.Serializable;
    10. /**
    11. * @Program: SpringBoot
    12. * @ClassName User
    13. * @Author: liutao
    14. * @Description: 用户类
    15. * @Create: 2023-06-11 17:22
    16. * @Version 1.0
    17. **/
    18. @Data
    19. @AllArgsConstructor
    20. @NoArgsConstructor
    21. @TableName("sys_user")
    22. public class User implements Serializable {
    23. private static final long serialVersionUID = 1L;
    24. @TableId(type = IdType.AUTO)
    25. @ApiModelProperty("用户id")
    26. private Integer id;
    27. @ApiModelProperty("用户名")
    28. private String username;
    29. @ApiModelProperty("密码")
    30. private String password;
    31. @ApiModelProperty("性别")
    32. private int sex;
    33. @ApiModelProperty("年龄")
    34. private int age;
    35. }

    3,实现UserDetails接口并重写所有方法

    1. package com.security.domain;
    2. import com.alibaba.fastjson.annotation.JSONField;
    3. import lombok.Data;
    4. import lombok.NoArgsConstructor;
    5. import org.springframework.security.core.GrantedAuthority;
    6. import org.springframework.security.core.authority.SimpleGrantedAuthority;
    7. import org.springframework.security.core.userdetails.UserDetails;
    8. import java.util.Collection;
    9. import java.util.List;
    10. import java.util.stream.Collectors;
    11. /**
    12. * @Program: SpringBoot
    13. * @ClassName LoginUser
    14. * @Author: liutao
    15. * @Description: 登录用户
    16. * @Create: 2023-06-11 18:17
    17. * @Version 1.0
    18. **/
    19. @Data
    20. @NoArgsConstructor
    21. public class LoginUser implements UserDetails {
    22. private User user;
    23. private List permissions;
    24. @JSONField(serialize = false)
    25. private List authorities;
    26. public LoginUser(User user, List permissions) {
    27. this.user = user;
    28. this.permissions = permissions;
    29. }
    30. @Override
    31. public Collectionextends GrantedAuthority> getAuthorities() {
    32. if (authorities != null) {
    33. return authorities;
    34. }
    35. // authorities = new ArrayList();
    36. // // 封装权限信息 -》GrantedAuthority
    37. // permissions.forEach(s -> {
    38. // SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(s);
    39. // authorities.add(simpleGrantedAuthority);
    40. // });
    41. authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
    42. return authorities;
    43. }
    44. @Override
    45. public String getPassword() {
    46. return user.getPassword();
    47. }
    48. @Override
    49. public String getUsername() {
    50. return user.getUsername();
    51. }
    52. @Override
    53. public boolean isAccountNonExpired() {
    54. return true;
    55. }
    56. @Override
    57. public boolean isAccountNonLocked() {
    58. return true;
    59. }
    60. @Override
    61. public boolean isCredentialsNonExpired() {
    62. return true;
    63. }
    64. @Override
    65. public boolean isEnabled() {
    66. return true;
    67. }
    68. }

    4,创建业务接口实现 UserDetailService接口 重写loadUserByUser(String s) 完成业务认证和授权

    1. package com.security.service.impl;
    2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    3. import com.security.domain.LoginUser;
    4. import com.security.domain.Role;
    5. import com.security.domain.User;
    6. import com.security.domain.UserRole;
    7. import com.security.mapper.RoleMapper;
    8. import com.security.mapper.UserMapper;
    9. import com.security.mapper.UserRoleMapper;
    10. import org.springframework.beans.factory.annotation.Autowired;
    11. import org.springframework.security.core.userdetails.UserDetails;
    12. import org.springframework.security.core.userdetails.UserDetailsService;
    13. import org.springframework.security.core.userdetails.UsernameNotFoundException;
    14. import org.springframework.stereotype.Service;
    15. import java.util.ArrayList;
    16. import java.util.Collections;
    17. import java.util.List;
    18. import java.util.Objects;
    19. import java.util.stream.Collectors;
    20. /**
    21. * @Program: SpringBoot
    22. * @ClassName service
    23. * @Author: liutao
    24. * @Description: 用户业务接口
    25. * @Create: 2023-06-11 17:43
    26. * @Version 1.0
    27. **/
    28. @Service
    29. public class UserDetailsServiceImpl implements UserDetailsService {
    30. @Autowired
    31. private UserMapper mapper;
    32. @Autowired
    33. private UserRoleMapper userRoleMapper;
    34. @Autowired
    35. private RoleMapper roleMapper;
    36. @Override
    37. public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    38. LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper();
    39. lambdaQueryWrapper.eq(User::getUsername, s);
    40. User user = mapper.selectOne(lambdaQueryWrapper);
    41. if (Objects.isNull(user)) {
    42. throw new UsernameNotFoundException("用户不存在");
    43. }
    44. //TODO 查询对应角色和权限信息 注意角色前一定要以“ROLE_”开头
    45. // List auths = new ArrayList(Arrays.asList("ROLE_USER","sys:user"));
    46. List auths = new ArrayList(Collections.emptyList());
    47. // 通过用户id获取当前用户所有角色id
    48. List ids = userRoleMapper.selectList(
    49. new LambdaQueryWrapper()
    50. .eq(UserRole::getUserId, user.getId())
    51. )
    52. .stream()
    53. .map(UserRole::getRoleId)
    54. .collect(Collectors.toList());
    55. // 获取角色
    56. List roles = roleMapper.selectBatchIds(ids)
    57. .stream()
    58. .map(Role::getFlag)
    59. .collect(Collectors.toList());
    60. auths.addAll(roles);
    61. auths.add("sys:user");
    62. return new LoginUser(user, auths);
    63. }
    64. }

    6,配置核心配置文件

    1. package com.security.config;
    2. import com.security.filter.JwtAuthenticationTokenFilter;
    3. import com.security.service.impl.UserDetailsServiceImpl;
    4. import org.springframework.beans.factory.annotation.Autowired;
    5. import org.springframework.context.annotation.Bean;
    6. import org.springframework.context.annotation.Configuration;
    7. import org.springframework.security.authentication.AuthenticationManager;
    8. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    9. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    10. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    11. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    12. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    13. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    14. import org.springframework.security.crypto.password.PasswordEncoder;
    15. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    16. /**
    17. * @Program: SpringBoot
    18. * @ClassName SecurityConfig
    19. * @Author: liutao
    20. * @Description: security配置类
    21. * @Create: 2023-06-11 17:39
    22. * @Version 1.0
    23. **/
    24. @Configuration
    25. @EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)
    26. @EnableWebSecurity
    27. public class SecurityConfig extends WebSecurityConfigurerAdapter {
    28. @Autowired
    29. private UserDetailsServiceImpl userDetailsService;
    30. @Autowired
    31. private PasswordEncoder passwordEncoder;
    32. @Autowired
    33. private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    34. @Override
    35. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    36. // 自定义用户认证
    37. // auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
    38. // 数据库用户认证
    39. auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    40. }
    41. /**
    42. * anyRequest | 匹配所有请求路径
    43. * access | SpringEl表达式结果为true时可以访问
    44. * anonymous | 匿名可以访问
    45. * denyAll | 用户不能访问
    46. * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
    47. * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
    48. * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
    49. * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
    50. * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
    51. * hasRole | 如果有参数,参数表示角色,则其角色可以访问
    52. * permitAll | 用户可以任意访问
    53. * rememberMe | 允许通过remember-me登录的用户访问
    54. * authenticated | 用户登录后可访问
    55. */
    56. @Override
    57. protected void configure(HttpSecurity http) throws Exception {
    58. http
    59. // CSRF禁用,因为不使用session
    60. .csrf().disable()
    61. // 禁用HTTP响应标头
    62. .headers().cacheControl().disable().and()
    63. // 过滤请求
    64. .authorizeRequests()
    65. // 对于登录toLogin 允许匿名访问
    66. .mvcMatchers("/toLogin").permitAll().mvcMatchers("/css/**", "/js/**", "/swagger-resources/**", "/webjars/**", "/v2/**", "/doc.html", "/img/**", "/favicon.ico").permitAll()
    67. // .antMatchers("/**")
    68. // .hasAnyRole("USER")
    69. // 除上面外的所有请求全部需要鉴权认证
    70. .anyRequest().authenticated();
    71. // .and()
    72. // .formLogin()
    73. // .loginProcessingUrl("/toLogin")
    74. // .successForwardUrl("/home")
    75. // .and()
    76. // .httpBasic();
    77. http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    78. }
    79. @Bean
    80. @Override
    81. protected AuthenticationManager authenticationManager() throws Exception {
    82. return super.authenticationManager();
    83. }
    84. @Bean
    85. public BCryptPasswordEncoder passwordEncoder() {
    86. return new BCryptPasswordEncoder();
    87. }
    88. }

    7,抽离web接口代码

    1. package com.security.service.impl;
    2. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    3. import com.security.common.Result;
    4. import com.security.domain.LoginUser;
    5. import com.security.domain.User;
    6. import com.security.domain.dto.UserDto;
    7. import com.security.domain.vo.UserVo;
    8. import com.security.mapper.UserMapper;
    9. import com.security.service.UserService;
    10. import com.security.utils.JwtUtil;
    11. import com.security.utils.RedisUtil;
    12. import org.slf4j.Logger;
    13. import org.slf4j.LoggerFactory;
    14. import org.springframework.beans.BeanUtils;
    15. import org.springframework.beans.factory.annotation.Autowired;
    16. import org.springframework.security.authentication.AuthenticationManager;
    17. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    18. import org.springframework.security.core.Authentication;
    19. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    20. import org.springframework.stereotype.Service;
    21. import java.util.Objects;
    22. import java.util.concurrent.TimeUnit;
    23. /**
    24. * @Program: SpringBoot
    25. * @ClassName UserServiceImpl
    26. * @Author: liutao
    27. * @Description:
    28. * @Create: 2023-06-12 00:51
    29. * @Version 1.0
    30. **/
    31. @Service
    32. public class UserServiceImpl extends ServiceImpl implements UserService {
    33. private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
    34. @Autowired
    35. private UserMapper mapper;
    36. @Autowired
    37. private RedisUtil redisUtil;
    38. @Autowired
    39. private AuthenticationManager authenticationManager;
    40. @Autowired
    41. private BCryptPasswordEncoder passwordEncoder;
    42. @Override
    43. public UserVo login(UserDto userDto) {
    44. UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDto.getUsername(), userDto.getPassword());
    45. Authentication authentication = authenticationManager.authenticate(token);
    46. if (Objects.isNull(authentication)) {
    47. throw new RuntimeException("登录失败!");
    48. }
    49. LoginUser loginUser = (LoginUser) authentication.getPrincipal();
    50. if (!passwordEncoder.matches(userDto.getPassword(), loginUser.getPassword())) {
    51. throw new RuntimeException("密码错误");
    52. }
    53. UserVo userVo = new UserVo();
    54. BeanUtils.copyProperties(loginUser.getUser(), userVo);
    55. String jwtToken = JwtUtil.getToken(userVo.getId().toString());
    56. String redisKey = "login:" + userVo.getId().toString();
    57. log.info("当前登录用户:{}",loginUser);
    58. redisUtil.set(redisKey, loginUser,20L, TimeUnit.MINUTES);
    59. userVo.setToken(jwtToken);
    60. return userVo;
    61. }
    62. }

    8,web接口

    1. package com.security.controller;
    2. import com.security.common.Result;
    3. import com.security.domain.dto.UserDto;
    4. import com.security.domain.vo.UserVo;
    5. import com.security.service.UserService;
    6. import org.springframework.beans.factory.annotation.Autowired;
    7. import org.springframework.security.access.prepost.PreAuthorize;
    8. import org.springframework.web.bind.annotation.*;
    9. /**
    10. * @author TAOGE
    11. */
    12. @RestController
    13. public class BasicController {
    14. @Autowired
    15. private UserService userService;
    16. @PostMapping("/toLogin")
    17. public Result login(@RequestBody UserDto userDto) {
    18. return Result.success("登录成功", userService.login(userDto));
    19. }
    20. @PreAuthorize("hasRole('ADMIN')")
    21. @GetMapping("/hasRole")
    22. public Result hasRole() {
    23. return Result.success("ADMIN");
    24. }
    25. @PreAuthorize("hasAuthority('sys:user')")
    26. @GetMapping("/hasPerm")
    27. public Result hasPerm() {
    28. return Result.success("sys:user");
    29. }
    30. }

    测试

    1,启动服务

    2,访问 /hasRole接口

    3,认证授权

    4,携带token访问 /hasRole 和 /hasPerm接口

    结尾

    到这里我们今天的SpringBoot整合和尝鲜SpringSecurity就结束了!!

    欢迎双击点赞 666!!!

    everybody see you!!!

  • 相关阅读:
    在kubernetes+istio中通过FQDN请求Nacos服务
    java虚拟机详解篇七(虚拟机线程)
    浅谈ClickHouse安全性和权限管理
    虚拟机Linux+Ubuntu操作系统 如何在虚拟机上安装docker VMPro 2024在线激活资源
    React 官网为什么那么快?
    一文讲清楚Java面向对象的继承关系
    为什么手动采购管理会危及你的工作流程?
    讯飞开放平台--星火认知大模型--开发技术文档--js实例代码详解
    【动态规划 状态机dp 性能优化】3098. 求出所有子序列的能量和
    阿里工作8年,肝到P7就剩这份学习笔记了,已助朋友拿到10个Offer
  • 原文地址:https://blog.csdn.net/LT19990304/article/details/136407281