[关闭]
@File 2019-10-19T07:28:07.000000Z 字数 20162 阅读 89

spring-security 权限管理

java


一、依赖

  1. <!-- security -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-security</artifactId>
  5. </dependency>
  6. <!-- social -->
  7. <dependency>
  8. <groupId>org.springframework.social</groupId>
  9. <artifactId>spring-social-web</artifactId>
  10. <version>1.1.6.RELEASE</version>
  11. </dependency>
  12. <!-- jdbc -->
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-jdbc</artifactId>
  16. </dependency>

二、yml 配置

  1. spring:
  2. datasource:
  3. url: jdbc:mysql://47.107.167.205:8888/test?useSSL=false&serverTimezone=UTC
  4. username: root
  5. password:
  6. driver-class-name: com.mysql.cj.jdbc.Driver

三、创建 configure

  1. @Configuration
  2. public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Resource
  4. private DataSource dataSource;
  5. /**
  6. * 查询逻辑
  7. */
  8. @Resource
  9. private UserSecurityService userSecurityService;
  10. /**
  11. * 验证码过滤类
  12. */
  13. @Resource
  14. private ImageCodeValidateFilter imageCodeValidateFilter;
  15. /**
  16. * 继承 `WebSecurityConfigurerAdapter` 并重写 `configure` 方法
  17. */
  18. @Override
  19. protected void configure(HttpSecurity http) throws Exception {
  20. // 请求前执行过滤(四、7. 验证码过滤)
  21. http.addFilterBefore(imageCodeValidateFilter, UsernamePasswordAuthenticationFilter.class)
  22. // ====== 基础登录设置 ====== //
  23. .formLogin() // 采用表单登录
  24. .loginPage("/login.html") // 登录页面
  25. .loginProcessingUrl("/authentication/form") // 请求接口(不用实现,自动调用 四、1.登录)
  26. // 成功逻辑(可调用 四、3.登录成功类)
  27. .successHandler((req,resp,exception) -> {
  28. resp.setContentType("applicatoin/json;charset=utf-8");
  29. resp.getWriter().write("登录成功");
  30. })
  31. // 失败逻辑(可调用 四、4.登录失败类)
  32. .failureHandler((req,resp,execption) -> {
  33. resp.setContentType("applicatoin/json;charset=utf-8");
  34. resp.getWriter().write("登录失败");
  35. })
  36. // ====== 退出登录 ====== //
  37. .and()
  38. .logout() // 开启访问 /logout 实现功能
  39. .logoutSuccessUrl("/login.html") // 退出后跳转的页面
  40. // ====== 权限认证(可选) ====== //
  41. .and() // 功能分隔,表示进行其他的配置
  42. .authorizeRequests() // 表示所有的都需要认证
  43. .antMatchers("/login.html", "/js/**", "/css/**") // 白名单
  44. .permitAll()
  45. .anyRequest() // 对于所有的请求
  46. .authenticated() // 认证后才能访问
  47. // ====== 记住登录状态(可选) ====== //
  48. // 前端传参 remember-me:true|false
  49. .and()
  50. .rememberMe() // 功能开启
  51. .tokenValiditySeconds(360000) // 记录时长
  52. .tokenRepository(persistentTokenRepository()) // 指定token库
  53. .userDetailsService(userSecurityService)
  54. // .alwaysRemember(true) // true 时只能记住
  55. // ====== 关闭跨站请求伪造功能(必须) ====== //
  56. .and()
  57. .csrf().disable();
  58. }
  59. /**
  60. * 配置密码加密规则
  61. */
  62. @Bean
  63. public PasswordEncoder passwordEncoder() {
  64. return new BCryptPasswordEncoder();
  65. }
  66. /**
  67. * 配置token验证
  68. */
  69. @Bean
  70. public PersistentTokenRepository persistentTokenRepository() {
  71. JdbcTokenRepositoryImpl jdbcTokenRepository
  72. = new JdbcTokenRepositoryImpl();
  73. jdbcTokenRepository.setDataSource(dataSource);
  74. // 是否自动建表
  75. jdbcTokenRepository.setCreateTableOnStartup(false);
  76. return jdbcTokenRepository;
  77. }
  78. }

四、辅助类

1. 登录逻辑

  1. @Component
  2. public class UserSecurityService implements UserDetailsService {
  3. /**
  4. * service 层
  5. */
  6. @Resource
  7. private SysUserService sysUserService;
  8. @Override
  9. public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
  10. // 通过用户名查出数据(三、2.通过用户名获取数据)
  11. SysUser user = sysUserService.getUser(name);
  12. // 通过查询结果的密码做比对
  13. User admin = new User(
  14. // 登录名
  15. name,
  16. // 用户密码
  17. user.getPassword(),
  18. // 权限(用于 八、权限验证),必须 ROLE_ 前缀
  19. Arrays.asList(new SimpleGrantedAuthority("ROLE_admin")
  20. ));
  21. return admin;
  22. }
  23. }

2. 通过用户名获取数据

  1. @Service
  2. public class SysUserService {
  3. @Resource
  4. private JdbcTemplate jdbcTemplate;
  5. public SysUser getUser(String name) {
  6. String sql = "SELECT * FROM `sys_user` WHERE `username` = ?";
  7. SysUser sysUser = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(SysUser.class), name);
  8. return sysUser;
  9. }
  10. }

3. 登录成功

  1. @Component
  2. public class MySuccessAuthenticationHandler implements AuthenticationSuccessHandler {
  3. /**
  4. * 登陆成功
  5. * @param request 请求对象
  6. * @param response 响应对象
  7. * @param authentication
  8. * @throws IOException
  9. * @throws ServletException
  10. */
  11. @Override
  12. public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
  13. // 成功逻辑
  14. response.setContentType("applicatoin/json;charset=utf-8");
  15. response.getWriter().write("登录成功");
  16. }
  17. }

4. 登录失败

  1. @Component
  2. public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
  3. /**
  4. * 登陆失败
  5. * @param request 请求对象
  6. * @param response 响应对象
  7. * @param exception 失败和异常信息
  8. * @throws IOException
  9. * @throws ServletException
  10. */
  11. @Override
  12. public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
  13. // 失败逻辑
  14. response.setContentType("applicatoin/json;charset=utf-8");
  15. response.getWriter().write("登录成功");
  16. }
  17. }

5. 验证码父类(抽象父类)

  1. @Data
  2. abstract class ValidationCode implements Serializable {
  3. /**
  4. * 验证码
  5. */
  6. private String code;
  7. /**
  8. * 有效时间
  9. */
  10. private LocalDateTime expire;
  11. /**
  12. * 有参构造
  13. * @param seconds 验证码存活时间
  14. */
  15. ValidationCode(int seconds) {
  16. // 执行子类的验证码生成逻辑
  17. code = setValidationCode();
  18. // 赋予存活时间
  19. setExpire(seconds);
  20. }
  21. /**
  22. * 验证码生成逻辑
  23. */
  24. abstract protected String setValidationCode();
  25. /**
  26. * 判断验证时间是否有效
  27. * @return true | false
  28. */
  29. public boolean isExpire() {
  30. return LocalDateTime.now().isAfter(getExpire());
  31. }
  32. /**
  33. * 修改有效时间
  34. * @param seconds 秒
  35. */
  36. public void setExpire(int seconds) {
  37. this.expire = LocalDateTime.now().plusSeconds(seconds);
  38. }
  39. }

6. 图片验证码(依赖 四、5.父类)

  1. @Data
  2. public class ImageCode extends ValidationCode {
  3. /**
  4. * 验证码图片本身
  5. */
  6. private BufferedImage image;
  7. /**
  8. * 构造方法直接继承父类
  9. * @param seconds 存活时间
  10. */
  11. public ImageCode(int seconds) {
  12. super(seconds);
  13. }
  14. /**
  15. * 编写生成图片验证码
  16. * @return 验证码
  17. */
  18. @Override
  19. protected String setValidationCode() {
  20. int width = 67;
  21. int height = 23;
  22. BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
  23. Graphics g = image.getGraphics();
  24. Random random = new Random();
  25. g.setColor(getRandColor(200, 250));
  26. g.fillRect(0, 0, width, height);
  27. g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
  28. g.setColor(getRandColor(160, 200));
  29. for (int i = 0; i < 155; i++) {
  30. int x = random.nextInt(width);
  31. int y = random.nextInt(height);
  32. int xl = random.nextInt(12);
  33. int yl = random.nextInt(12);
  34. g.drawLine(x, y, x + xl, y + yl);
  35. }
  36. // 随机生成文字
  37. String sRand = "";
  38. for (int i = 0; i < 4; i++) {
  39. String rand = String.valueOf(random.nextInt(10));
  40. sRand += rand;
  41. g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
  42. g.drawString(rand, 13 * i + 6, 16);
  43. }
  44. g.dispose();
  45. // 修改图片信息
  46. setImage(image);
  47. // 给父类返回验证码
  48. return sRand;
  49. }
  50. /**
  51. * 生成随机背景条纹
  52. *
  53. * @param fc rgb色值
  54. * @param bc rgb色值
  55. * @return
  56. */
  57. private Color getRandColor(int fc, int bc) {
  58. Random random = new Random();
  59. if (fc > 255) {
  60. fc = 255;
  61. }
  62. if (bc > 255) {
  63. bc = 255;
  64. }
  65. int r = fc + random.nextInt(bc - fc);
  66. int g = fc + random.nextInt(bc - fc);
  67. int b = fc + random.nextInt(bc - fc);
  68. return new Color(r, g, b);
  69. }
  70. }

7. 短信验证码

8. 验证码过滤器

  1. @Component
  2. public class ImageCodeValidateFilter extends OncePerRequestFilter {
  3. /**
  4. * session 工具类
  5. */
  6. private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
  7. @Override
  8. protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp, FilterChain fc) throws ServletException, IOException {
  9. // 只应用于 /authentication/form 的 post
  10. if(req.getMethod().equals("POST") && "/authentication/form".equals(req.getRequestURI())) {
  11. // 取到验证码
  12. ImageCode imageCode = (ImageCode)sessionStrategy.getAttribute(new ServletWebRequest(req),ValidataCodeController.VALIDATE_CODE_KEY);
  13. // 验证码是否过期 || 验证码是否正确
  14. if(imageCode.isExpire() || !Predicate.isEqual(imageCode.getCode()).test(req.getParameter("validateCode"))){
  15. // 验证失败
  16. resp.getWriter().write("error");
  17. return;
  18. }
  19. }
  20. // 验证成功,继续执行后续代码
  21. fc.doFilter(req, resp);
  22. }
  23. }

五、图片验证码获取接口

  1. @Controller
  2. @RequestMapping("/validate")
  3. public class ImageCodeController {
  4. /**
  5. * session 的工具类
  6. */
  7. private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
  8. /**
  9. * session 键名
  10. */
  11. public static final String VALIDATE_CODE_KEY = "IMAGE_CODE_KEY";
  12. /**
  13. * 获取验证码接口
  14. * @param req 请求对象
  15. * @param resp 响应对象
  16. * @throws IOException
  17. */
  18. @GetMapping("/code")
  19. public void validateCode(HttpServletRequest req, HttpServletResponse resp) throws IOException {
  20. // 生成验证码
  21. ImageCode imageCode = new ImageCode(60);
  22. // 将ImageCode存入到session
  23. sessionStrategy.setAttribute(new ServletWebRequest(req), VALIDATE_CODE_KEY, imageCode);
  24. //将图片写入前端
  25. ImageIO.write(imageCode.getImage(), "JPEG", resp.getOutputStream());
  26. }
  27. }

六、手机验证(自定义配置)

1. 依赖

  1. <dependency>
  2. <groupId>org.apache.commons</groupId>
  3. <artifactId>commons-text</artifactId>
  4. <version>1.8</version>
  5. </dependency>
  6. <!-- 阿里云发送短信 -->
  7. <dependency>
  8. <groupId>com.aliyun</groupId>
  9. <artifactId>aliyun-java-sdk-core</artifactId>
  10. <version>4.0.3</version>
  11. </dependency>

2. 创建过滤器(两个)

1. 解析请求:

  1. public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
  2. // ~ Static fields/initializers
  3. // =====================================================================================
  4. public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
  5. private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;
  6. private boolean postOnly = true;
  7. // ~ Constructors
  8. // ===================================================================================================
  9. public SmsAuthenticationFilter() {
  10. super(new AntPathRequestMatcher("/authentication/sms", "POST"));
  11. }
  12. // ~ Methods
  13. // ========================================================================================================
  14. @Override
  15. public Authentication attemptAuthentication(HttpServletRequest request,
  16. HttpServletResponse response) throws AuthenticationException {
  17. if (postOnly && !request.getMethod().equals("POST")) {
  18. throw new AuthenticationServiceException(
  19. "Authentication method not supported: " + request.getMethod());
  20. }
  21. String mobile = obtainMobile(request);
  22. if (mobile == null) {
  23. mobile = "";
  24. }
  25. // 反射
  26. mobile = mobile.trim();
  27. SmsAuthenticationToken authRequest = new SmsAuthenticationToken(mobile);
  28. // Allow subclasses to set the "details" property
  29. setDetails(request, authRequest);
  30. return this.getAuthenticationManager().authenticate(authRequest);
  31. }
  32. protected String obtainMobile(HttpServletRequest request) {
  33. return request.getParameter(mobileParameter);
  34. }
  35. /**
  36. * Provided so that subclasses may configure what is put into the authentication
  37. * request's details property.
  38. *
  39. * @param request that an authentication request is being created for
  40. * @param authRequest the authentication request object that should have its details
  41. * set
  42. */
  43. protected void setDetails(HttpServletRequest request,
  44. SmsAuthenticationToken authRequest) {
  45. authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
  46. }
  47. /**
  48. * Sets the parameter name which will be used to obtain the username from the login
  49. * request.
  50. *
  51. * @param mobileParameter the parameter name. Defaults to "username".
  52. */
  53. public void setUsernameParameter(String mobileParameter) {
  54. Assert.hasText(mobileParameter, "Username parameter must not be empty or null");
  55. this.mobileParameter = mobileParameter;
  56. }
  57. /**
  58. * Defines whether only HTTP POST requests will be allowed by this filter. If set to
  59. * true, and an authentication request is received which is not a POST request, an
  60. * exception will be raised immediately and authentication will not be attempted. The
  61. * <tt>unsuccessfulAuthentication()</tt> method will be called as if handling a failed
  62. * authentication.
  63. * <p>
  64. * Defaults to <tt>true</tt> but may be overridden by subclasses.
  65. */
  66. public void setPostOnly(boolean postOnly) {
  67. this.postOnly = postOnly;
  68. }
  69. public final String getMobileParameter() {
  70. return mobileParameter;
  71. }
  72. }

2. 验证码校验:

  1. @Component
  2. public class SmsCodeValidateFilter extends OncePerRequestFilter {
  3. /**
  4. * session 工具类
  5. */
  6. private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
  7. @Override
  8. protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp, FilterChain fc) throws ServletException, IOException {
  9. // 只应用于 /authentication/sms 的 post
  10. if(req.getMethod().equals("POST") && "/authentication/sms".equals(req.getRequestURI())) {
  11. // 取到验证码
  12. SmsCode smsCode = (SmsCode)sessionStrategy.getAttribute(new ServletWebRequest(req), SmsCodeController.VALIDATE_CODE_KEY);
  13. // 验证码是否过期 || 验证码是否正确
  14. if(smsCode.isExpire() || !Predicate.isEqual(smsCode.getCode()).test(req.getParameter("smsCode"))){
  15. // 验证失败
  16. resp.getWriter().write("error");
  17. return;
  18. }
  19. }
  20. // 验证成功,继续执行后续代码
  21. fc.doFilter(req, resp);
  22. }
  23. }

3. 创建 token 类

  1. public class SmsAuthenticationToken extends AbstractAuthenticationToken {
  2. private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
  3. // ~ Instance fields
  4. // ================================================================================================
  5. /**
  6. * 在验证之前封装电话信息,
  7. * 在验证之后存储 用户信息
  8. */
  9. private final Object principal;
  10. // ~ Constructors
  11. // ===================================================================================================
  12. /**
  13. * This constructor can be safely used by any code that wishes to create a
  14. * <code>UsernamePasswordAuthenticationToken</code>, as the {@link #isAuthenticated()}
  15. * will return <code>false</code>.
  16. */
  17. public SmsAuthenticationToken(Object principal) {
  18. super(null);
  19. this.principal = principal;
  20. setAuthenticated(false);
  21. }
  22. /**
  23. * This constructor should only be used by <code>AuthenticationManager</code> or
  24. * <code>AuthenticationProvider</code> implementations that are satisfied with
  25. * producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
  26. * authentication token.
  27. *
  28. * @param principal
  29. * @param authorities
  30. */
  31. public SmsAuthenticationToken(Object principal,
  32. Collection<? extends GrantedAuthority> authorities) {
  33. super(authorities);
  34. this.principal = principal;
  35. super.setAuthenticated(true); // must use super, as we override
  36. }
  37. @Override
  38. public Object getCredentials() {
  39. return null;
  40. }
  41. @Override
  42. public Object getPrincipal() {
  43. return this.principal;
  44. }
  45. @Override
  46. public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
  47. if (isAuthenticated) {
  48. throw new IllegalArgumentException(
  49. "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
  50. }
  51. super.setAuthenticated(false);
  52. }
  53. @Override
  54. public void eraseCredentials() {
  55. super.eraseCredentials();
  56. }
  57. }

4. 创建 provider 类

  1. public class SmsAuthenticatoinProvider implements AuthenticationProvider {
  2. private UserSecurityService userSecurityService;
  3. /**
  4. *
  5. * @param authentication
  6. * @return
  7. * @throws AuthenticationException
  8. */
  9. @Override
  10. public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  11. SmsAuthenticationToken smsAuthenticationToken = (SmsAuthenticationToken)authentication;
  12. // 获取到电话
  13. String mobile = (String)smsAuthenticationToken.getPrincipal();
  14. UserDetails userDetails = userSecurityService.loadUserByUsername(mobile);
  15. SmsAuthenticationToken token = new SmsAuthenticationToken(userDetails, userDetails.getAuthorities());
  16. // 设置用户的其他的详细信息(登录一些)
  17. token.setDetails(smsAuthenticationToken.getDetails());
  18. return token;
  19. }
  20. /**
  21. * 该方法就是来判断,该Provider要处理哪一个Filter丢过来的Token,
  22. * 返回true, 上面的方法 authenticate(Authentication authentication)
  23. */
  24. @Override
  25. public boolean supports(Class<?> authentication) {
  26. // 判断类型是否一致
  27. return authentication.isAssignableFrom(SmsAuthenticationToken.class);
  28. }
  29. }

5. 创建自定义配置类

  1. @Configuration
  2. public class SmsAuthenticationConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain,HttpSecurity> {
  3. /**
  4. * 登录逻辑
  5. */
  6. @Resource
  7. private UserSecurityService userSecurityService;
  8. /**
  9. * 登录成功
  10. */
  11. @Resource
  12. private MySuccessAuthenticationHandler successHandler;
  13. /**
  14. * 登录失败
  15. */
  16. @Resource
  17. private MyAuthenticationFailureHandler failureHandler;
  18. /**
  19. * 配置
  20. * @param http 配置对象
  21. * @throws Exception
  22. */
  23. @Override
  24. public void configure(HttpSecurity http) throws Exception {
  25. // 创建自定义过滤实例
  26. SmsAuthenticationFilter filter = new SmsAuthenticationFilter();
  27. // 创建自定义provider实例
  28. SmsAuthenticatoinProvider provider = new SmsAuthenticatoinProvider();
  29. // 设置AuthenticatoinManager, 因为filter和Provider中间的桥梁就是 AuthenticationManager
  30. filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
  31. // 登录成功
  32. filter.setAuthenticationSuccessHandler(successHandler);
  33. // 登录失败
  34. filter.setAuthenticationFailureHandler(failureHandler);
  35. // 绑定登录查询逻辑
  36. provider.setUserSecurityService(userSecurityService);
  37. // 绑定provider
  38. http.authenticationProvider(provider)
  39. // 绑定过滤器
  40. .addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
  41. }
  42. }

5. 修改主 configure

  1. @Configuration
  2. public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
  3. /**
  4. * 自定义的配置类
  5. */
  6. @Resource
  7. private SmsAuthenticationConfig smsAuthenticationConfig;
  8. /**
  9. * 手机验证码过滤器
  10. */
  11. @Resource
  12. private SmsCodeValidateFilter smsCodeValidateFilter;
  13. @Override
  14. protected void configure(HttpSecurity http) throws Exception {
  15. // 三个修改
  16. http.addFilterBefore()
  17. // 添加手机验证码过滤器
  18. .addFilterBefore(smsCodeValidateFilter, UsernamePasswordAuthenticationFilter.class)
  19. .xxx()
  20. .and()
  21. .xxx()
  22. // 记得设置白名单
  23. .antMatchers("/Sms/code")
  24. .xxx()
  25. .and()
  26. .csrf().disable()
  27. // 在这里接入 apply()
  28. .apply(smsAuthenticationConfig);
  29. }
  30. }

6. 短信验证码类

  1. public class SmsCode extends ValidationCode {
  2. /**
  3. * 直接使用父类构造
  4. * @param seconds 存活时间
  5. */
  6. public SmsCode(int seconds) {
  7. super(seconds);
  8. }
  9. /**
  10. * 定义验证码生成逻辑
  11. * @return 验证码
  12. */
  13. @Override
  14. protected String setValidationCode() {
  15. // 生成随机数子的工具类
  16. RandomStringGenerator randomStringGenerator
  17. = new RandomStringGenerator.Builder().withinRange(new char[]{'0','9'}).build();
  18. return randomStringGenerator.generate(4);
  19. }
  20. }

7. service 层发送信息

  1. @Service
  2. public class SysUserService {
  3. /**
  4. * 封装好请求第三方接口的类
  5. */
  6. @Resource
  7. private SmsService smsService;
  8. public boolean sendSms(String code) {
  9. return smsService.send(code+"","mobile");
  10. }
  11. }

8. 短信验证码获取接口

  1. @Controller
  2. @RequestMapping("/validate")
  3. public class SmsCodeController {
  4. /**
  5. * service 层
  6. */
  7. @Resource
  8. private SysUserService sysUserService;
  9. /**
  10. * session 的工具类
  11. */
  12. private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
  13. /**
  14. * session 键名
  15. */
  16. public static final String VALIDATE_CODE_KEY = "Sms_CODE_KEY";
  17. /**
  18. * 获取验证码接口
  19. * @param req 请求对象
  20. * @param resp 响应对象
  21. * @throws IOException
  22. */
  23. @GetMapping("/code")
  24. public void validateCode(HttpServletRequest req, HttpServletResponse resp) throws IOException {
  25. // 生成验证码
  26. SmsCode SmsCode = new SmsCode(60);
  27. // 将SmsCode存入到session
  28. sessionStrategy.setAttribute(new ServletWebRequest(req), VALIDATE_CODE_KEY, SmsCode);
  29. // 请求发送短信
  30. sysUserService.sendSms();
  31. }
  32. }

七、session 集群管理

1. 依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.session</groupId>
  7. <artifactId>spring-session-core</artifactId>
  8. <version>2.1.9.RELEASE</version>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.session</groupId>
  12. <artifactId>spring-session-data-redis</artifactId>
  13. <version>2.1.9.RELEASE</version>
  14. </dependency>
  15. <dependency>
  16. <groupId>org.apache.commons</groupId>
  17. <artifactId>commons-pool2</artifactId>
  18. <version>2.7.0</version>
  19. </dependency>

2. yml 配置

  1. spring:
  2. # 基本的 redis 配置
  3. redis:
  4. port: 6379
  5. host: localhost
  6. password:
  7. # 连接池
  8. lettuce:
  9. pool:
  10. min-idle: 2
  11. max-active: 8
  12. session:
  13. # session 存储方式
  14. store-type: redis

3. 修改主 config

  1. @Configuration
  2. public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. http.xxx()
  6. .and()
  7. .sessionManagement() //
  8. .maximumSessions(1) // 表示一个用户只能有一个会话,不能重复登录
  9. // 被强制下线时执行的逻辑
  10. .expiredSessionStrategy(event -> {
  11. HttpServletResponse response = event.getResponse();
  12. response.setContentType("text/plain;charset=utf-8");
  13. response.getWriter().write("您已经在其他设备登录,强制下线");
  14. })
  15. // 要用两个 and() 做结束
  16. .and()
  17. .and()
  18. .xxx();
  19. }
  20. }

4. nginx 负载均衡配置

八、权限验证

1. 重温一下用户登录时的权限设置

  1. @Component
  2. public class UserSecurityService implements UserDetailsService {
  3. /**
  4. * service 层
  5. */
  6. @Resource
  7. private SysUserService sysUserService;
  8. @Override
  9. public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
  10. SysUser user = sysUserService.getUser(name);
  11. // 实例化 User 时第三个参数是 List 类型的权限集合
  12. User admin = new User(name, user.getPassword(),
  13. // 必须 ROLE_ 前缀,实际情况是通过数据动态生成的
  14. Arrays.asList(
  15. new SimpleGrantedAuthority("ROLE_admin"),
  16. new SimpleGrantedAuthority("ROLE_user")
  17. )
  18. );
  19. return admin;
  20. }
  21. }

2. 入口开启功能

securedEnabled: 开启 @Secured("ROLE_abc") 方式
jsr250Enabled: 开启 @RolesAllowed("admin") 方式
prePostEnabled: 开启 @PreAuthorize@PostAuthorize 方式

  1. @SpringBootApplication
  2. @EnableGlobalMethodSecurity(jsr250Enabled = true)
  3. public class SecurityApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(SecurityApplication.class, args);
  6. }
  7. }

3. config 配置中开启权限

  1. @Configuration
  2. public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. http.xxx()
  6. // 方式一:用户权限控制(推荐可用注解配置)
  7. .antMatchers("/user/**", "/dept/**").hasRole("admin") // 不能有ROLE_前缀
  8. // 方式二:操作权限控制(推荐用注解配置)
  9. .antMatchers("/user/delete").hasRole("user:delete")
  10. .and()
  11. // 无权限访问时的处理
  12. .exceptionHandling()
  13. .accessDeniedHandler((req,resp,exception) -> {
  14. // 处理逻辑
  15. resp.sendRedirect("/noAuth.html");
  16. })
  17. .xxx();
  18. }
  19. }

4. 注解开启权限

4.1 @RolesAllowed jsr250方式

  1. @RestController
  2. // 该类中所有方法都需要 user 权限
  3. @RolesAllowed("user")
  4. public class Demo {
  5. @GetMapping("/TestSecurity")
  6. // 该方法需要 admin 或 user 权限
  7. @RolesAllowed({"admin""user"})
  8. public String test() {
  9. return "test";
  10. }
  11. }

4.2 @Secured secured方式

  1. public interface UserService {
  2. @Secured("ROLE_user")
  3. List<User> findAllUsers();
  4. @Secured("ROLE_admin")
  5. void deleteUser(int id);
  6. }

4.3 @PreAuthorize 方法执行前

  1. /**
  2. * 对角色做权限控制
  3. */
  4. @PreAuthorize("hasRole('admin')") // 需要角色 admin
  5. @PreAuthorize("hasRole('admin') or hasRole('user')") // 需要角色 admin 或 user
  6. @PreAuthorize("hasRole('admin') and hasRole('user')") // 同时需要角色 admin 和 user
  7. @PreAuthorize("hasAnyRole('admin','user')") // 需要角色 admin 或 user
  8. /**
  9. * 对操作做权限控制
  10. */
  11. @PreAuthorize("hasAuthority('user:delete')") // 需要有 user:delete 操作权限
  12. @PreAuthorize("hasAuthority('user:delete') or hasAuthority('user:root')") // 需要有 user:delete 或 user:root 操作权限
  13. @PreAuthorize("hasAuthority('user:delete') and hasAuthority('user:root')") // 同时需要 user:delete 和 user:root 操作权限
  14. @PreAuthorize("hasAnyAuthority('user:delete','user:root')") // 需要有 user:delete 或 user:root 操作权限

4.4 @PostAuthorize 方法执行后

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注