当前位置:   article > 正文

spring-security安全框架(超精细版附带流程讲解图)_spring security

spring security

目录

一、回顾一下

二、security使用

2.1 覆盖掉默认配置「自定义配置」

2.2 如何自定义认证

2.3 纯纯自定义

2.4 jwt

2.5 官网认证流程

2.6 RBAC模型

4.1. 创建表结构

2.7 如何实现权限流程


一、回顾一下

  1. security干啥的?

    认证和授权

  2. 使用方式

    1. 引入依赖, 基于spring boot的下的使用.

    2. spring-boot-starter-security, 直接可以使用了.

  3. 观察一下

    1. 姿源分类

      1. 受保护的资源, 需要认证

      2. 公共方式, 不需要认证.

    2. 当我们把security引入到项目当中的时候,我们去访问一下受保护的资源,会弹出一个默认的一个登录界面.用户名称默认的是: user, 密码随机生成的.通过uuid生成的.如果认证成功,则直接跳转到要访问的接口.

  4. 基本原理

    1. SecurityAutoConfiguration, spring security自动配置类.默认配置.如果我们啥也不干,则直接走默认配置.界面了,用户名称和密码都是默认生成的.

    2. 如果想要覆盖掉默认配置,则我们用两种方案.

      1. 继承一个类WebSecurityConfigurerAdapter, 重写方法.

      2. 将SecurityFilterChain放到容器当中.

      1. /**
      2. * {@link Condition} for
      3. * {@link ConditionalOnDefaultWebSecurity @ConditionalOnDefaultWebSecurity}.
      4. *
      5. * @author Phillip Webb
      6. */
      7. class DefaultWebSecurityCondition extends AllNestedConditions {
      8. DefaultWebSecurityCondition() {
      9. super(ConfigurationPhase.REGISTER_BEAN);
      10. }
      11. @ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })
      12. static class Classes {
      13. }
      14.    // 当IoC容器当中没有WebSecurityConfigurerAdapter.class, SecurityFilterChain.class 这两个类的对象
      15.    // 则默认生效,否则默认配置不生效.
      16. @ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class, SecurityFilterChain.class })
      17. static class Beans {
      18. }
      19. }

    3. 默认的配置类

      SecurityProperties

      1. @ConfigurationProperties(prefix = "spring.security")
      2. public class SecurityProperties {}

    4. 对认证资源进行配置

      1. 可以针对某一些资源,不进行认证, 默认是都进行认证的.

      2. 此时我们就覆盖掉默认配置.

二、security使用

2.1 覆盖掉默认配置「自定义配置」

  1. public HttpSecurity authorizeRequests(
  2.        Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry> authorizeRequestsCustomizer)
  3.        throws Exception {
  4.    ApplicationContext context = getContext();
  5.    authorizeRequestsCustomizer
  6.           .customize(getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context)).getRegistry());
  7.    return HttpSecurity.this;
  8. }

认证成功之后的处理:

  1. public final T successHandler(AuthenticationSuccessHandler successHandler) {
  2.    this.successHandler = successHandler;
  3.    return getSelf();
  4. }
  1. package com.tingyi.configs;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  6. import org.springframework.security.core.Authentication;
  7. import org.springframework.security.web.SecurityFilterChain;
  8. import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
  9. import javax.servlet.ServletException;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletResponse;
  12. import java.io.IOException;
  13. import java.io.PrintWriter;
  14. import java.util.HashMap;
  15. import java.util.Map;
  16. /**
  17. * @author 听忆
  18. */
  19. @Configuration
  20. public class SecurityConfig {
  21.    @Bean
  22.    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  23.        return http.authorizeRequests(authorize ->
  24.                        authorize.mvcMatchers("/tom")
  25.                               .permitAll()
  26.                               .anyRequest()
  27.                               .authenticated())
  28.               .formLogin()
  29.                // .successForwardUrl("/success") // 默认的话,跳转到你在认证之前的请求.
  30.                // .defaultSuccessUrl("/success", true) // true,表示强制跳转到指定的url
  31.               .successHandler(new AuthenticationSuccessHandler() { // security提供给我们的,认证成功之后的处理.我们可以在这里返回json给前端.
  32.                    // 前后端分离项目使用的方式;
  33.                    @Override
  34.                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
  35.                        // 给前端返回一个json串.应用于前后端分离的项目.
  36.                        Map<String, Object> map = new HashMap<>();
  37.                        map.put("code", 0); // 状态码
  38.                        map.put("msg", "认证成功");
  39.                        map.put("authentication", authentication);
  40.                        PrintWriter writer = response.getWriter();
  41.                        String json = new ObjectMapper().writeValueAsString(map);
  42.                        writer.print(json);
  43.                   }
  44.               }).and()
  45.               .csrf(csrf -> csrf.disable())
  46.               .build();
  47.   }
  48. }

认证流程:

  • 浏览器输入了用户名称和密码 —> 服务器 –> security 进行认证, 怎么认证的?

    • UsernamePasswordAuthenticaionFilter

      • AbstractAuthenticationProcessingFilter


  • 我们去认证的时候,服务器把密码存储到哪里地了.

  • UserDetailsService

    • UserDetailsManager, 用户信息管理.接口.封装了对用户所有操作.

      • InMemoryUserDetailsManager, 基于内存实现的.也就是说,将用户信息都存储在内存当中了.

2.2 如何自定义认证

DaoAuthenticationProvider

  1. protected void additionalAuthenticationChecks(UserDetails userDetails,
  2.        UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
  3.    if (authentication.getCredentials() == null) {
  4.        this.logger.debug("Failed to authenticate since no credentials provided");
  5.        throw new BadCredentialsException(this.messages
  6.               .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
  7.   }
  8.    String presentedPassword = authentication.getCredentials().toString();
  9.    if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
  10.        this.logger.debug("Failed to authenticate since password does not match stored value");
  11.        throw new BadCredentialsException(this.messages
  12.               .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
  13.   }
  14. }

数据放到了内存当中,使用的是: InMemoryUserDetailsManager, 从内存读取数据,实际开发当中,数据源, 一般情况来自于数据库.也就是说, 我们存储用户名称和密码应该是存储在数据当中,咱们进行认证的时候,应该是从数据当中获取用户名称和密码.替换掉默认的: InMemoryUserDetailsManager.

通过查看,类关系图.发现有一个接口: UserDetailsService

  1. package org.springframework.security.core.userdetails;
  2. // 如果我们要自定义实现读取的数据源, 则必须实现这个接口,重写这个方法.
  3. public interface UserDetailsService {
  4. // 通过用户名称获取用户的详细信息.
  5.    // 返回值是一个UserDetails接口.实际上返回的应该是一个对象.
  6. UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
  7. }

​
​

  1. package org.springframework.security.core.userdetails;
  2. import java.io.Serializable;
  3. import java.util.Collection;
  4. import org.springframework.security.core.Authentication;
  5. import org.springframework.security.core.GrantedAuthority;
  6. /**
  7. * Provides core user information.
  8. *
  9. * <p>
  10. * Implementations are not used directly by Spring Security for security purposes. They
  11. * simply store user information which is later encapsulated into {@link Authentication}
  12. * objects. This allows non-security related user information (such as email addresses,
  13. * telephone numbers etc) to be stored in a convenient location.
  14. * <p>
  15. * Concrete implementations must take particular care to ensure the non-null contract
  16. * detailed for each method is enforced. See
  17. * {@link org.springframework.security.core.userdetails.User} for a reference
  18. * implementation (which you might like to extend or use in your code).
  19. *
  20. * @author Ben Alex
  21. * @see UserDetailsService
  22. * @see UserCache
  23. * 用户详细信息
  24. * 1. 用户名称
  25. * 2. 用户密码
  26. * 3. 用户的权限列表
  27. * 1. 角色信息
  28. * 2. 权限信息
  29. */
  30. public interface UserDetails extends Serializable {
  31. /**
  32. * Returns the authorities granted to the user. Cannot return <code>null</code>.
  33. * @return the authorities, sorted by natural key (never <code>null</code>)
  34. * 权限列表
  35. */
  36. Collection<? extends GrantedAuthority> getAuthorities();
  37. /**
  38. * Returns the password used to authenticate the user.
  39. * @return the password
  40. * 获取用户密码
  41. */
  42. String getPassword();
  43. /**
  44. * Returns the username used to authenticate the user. Cannot return
  45. * <code>null</code>.
  46. * @return the username (never <code>null</code>)
  47. * 用户名称
  48. */
  49. String getUsername();
  50. /**
  51. * Indicates whether the user's account has expired. An expired account cannot be
  52. * authenticated.
  53. * @return <code>true</code> if the user's account is valid (ie non-expired),
  54. * <code>false</code> if no longer valid (ie expired)
  55. * 账号状态是否是过期的.
  56. */
  57. boolean isAccountNonExpired();
  58. /**
  59. * Indicates whether the user is locked or unlocked. A locked user cannot be
  60. * authenticated.
  61. * @return <code>true</code> if the user is not locked, <code>false</code> otherwise
  62. */
  63. boolean isAccountNonLocked();
  64. /**
  65. * Indicates whether the user's credentials (password) has expired. Expired
  66. * credentials prevent authentication.
  67. * @return <code>true</code> if the user's credentials are valid (ie non-expired),
  68. * <code>false</code> if no longer valid (ie expired)
  69. */
  70. boolean isCredentialsNonExpired();
  71. /**
  72. * Indicates whether the user is enabled or disabled. A disabled user cannot be
  73. * authenticated.
  74. * @return <code>true</code> if the user is enabled, <code>false</code> otherwise
  75. */
  76. boolean isEnabled();
  77. }

User, spring security提供的一个类,这个类实现了UserDetails接口.

  1. // username,表示我们根据用户名称,从内存或者数据库查询出来的用户名称.
  2. // password, 从内存或者数据库当中查询出来的密码
  3. // authorities, 从内存或者数据库当中查询出来该用户名称对应的权限列表.
  4. public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
  5.    this(username, password, true, true, true, true, authorities);
  6. }

重要的接口和实现类:

  • UserDetailsService,

    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
    // 根据前端传递过来的用户名称,去数据当中查询出用户名称对应的详细信息,封装成UserDetails对象即可;
    • InMemoryUserDetailsManager,它是一个实现类,它表示从内存当中读取.

    • 我们如果要换成从数据库当中读取用户信息,则必须实现UserDetailsService接口,重写方法.查询出来的数据,封里成UserDetatils对象.

  • UserDetails, 表示定义用户的各种各样的信息.

    • 用户名称

    • 用户密码

    • 用户权限列表

    • 实现类: User, 在UserDetailsService方法, loadUserByUsername返回它即可;

如果, controller当中的login,直接调用Service层,此时需要我们自己处理,整个验证过程.

现在我们如果在userDetailsService实现类当中,进行相关的业务处理,将验证过程直接交给了security. 不用我们操心了.

2.3 纯纯自定义

  1. 根据流程来说, 要将从内存获取数据方式改更从数据库进行查询.

    自己定义一个UserDetailsService实现类,完成一个逻辑:

    ①. 根据用户名称去数据库查询出这个用户名称对应的数据.

    ②. 将查询出来的数据封装成UserDetails对象.

  1. package com.tingyi.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  3. import com.qf.entity.TbUser;
  4. import com.qf.mapper.ITbUserMapper;
  5. import org.springframework.security.core.GrantedAuthority;
  6. import org.springframework.security.core.userdetails.User;
  7. import org.springframework.security.core.userdetails.UserDetails;
  8. import org.springframework.security.core.userdetails.UserDetailsService;
  9. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  10. import org.springframework.stereotype.Service;
  11. import java.util.Collections;
  12. import java.util.List;
  13. /**
  14. * @author 听忆
  15. * 自定义读取过程,之前是从在内存当中,根据用户名称获取用户详情,现在我们从数据库当中进行获取.
  16. * mybatis plus 来读取一下.
  17. */
  18. @Service
  19. public class UserDetailsServiceImpl implements UserDetailsService {
  20.    private final ITbUserMapper tbUserMapper;
  21.    public UserDetailsServiceImpl(ITbUserMapper tbUserMapper) {
  22.        this.tbUserMapper = tbUserMapper;
  23.   }
  24.    @Override
  25.    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  26.        // 根据用户名称去数据库当中查找.
  27.        LambdaQueryWrapper<TbUser> queryWrapper = new LambdaQueryWrapper<>();
  28.        queryWrapper.eq(TbUser::getUsername, username);
  29.        // 查询出结果,根据咱们自己就没有处理它.
  30.        TbUser user = tbUserMapper.selectOne(queryWrapper); // 通过这个对象,获取密码.还有权限列表.
  31.        // 最终我们得把获取到的数据封装成UserDetails对象.交给spring security处理去.
  32.        List<GrantedAuthority> grantedAuthorityList = Collections.emptyList(); // 权限列表.
  33.        // 封装UserDetails对象.
  34.        // User是UserDetails实现类.所以咱们可以直接返回这个实现类对象.
  35.        return new User(username, user.getPassword(), grantedAuthorityList);
  36.   }
  37. }
  1. 手动完成认证

    整个spring security一共15个过滤器. 其中有一个负责账号密码认证的过滤器: UsernamePasswordAuthenticationFilter.

需要一个认证管理器:

  • AutenticationManager, 咱们是配置类,将它注入到IoC容器当中.

  1. @Bean
  2. public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
  3.    return authenticationConfiguration.getAuthenticationManager();
  4. }

直接调用认证方法:

  1. @Override
  2.    public Result login(String username, String password) {
  3.        try {
  4.            // 1. 将用户名称和密码封装成UsernamePasswordAuthenticationToken.
  5.            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
  6.                    new UsernamePasswordAuthenticationToken(username, password);
  7.            // 2. 调用AuthenticationManager提供认证方法.
  8.            // Authentication authenticate(Authentication authentication) throws AuthenticationException;
  9.            Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
  10.            // 3. 存储认证结果.
  11.            SecurityContextHolder.getContext().setAuthentication(authenticate);
  12.            return Result.success("认证成功", authenticate);
  13.       }catch (AuthenticationException e){
  14.            return Result.error("认证失败", e.getMessage());
  15.       }
  16.   }

  1. 更改配置文件

    得去执行我们自己的认证页面.

  1. @Bean
  2. public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  3.    return http.authorizeRequests(authorize ->
  4.                    authorize.mvcMatchers("/tom", "/login")
  5.                           .permitAll()
  6.                           .anyRequest()
  7.                           .authenticated())
  8.            // .formLogin() // 仅仅表示我使用表单验证, 但是配置用的都是默认的.
  9.           .formLogin(form -> form.loginPage("/login.html").permitAll()
  10.                   .successHandler(new AuthenticationSuccessHandler() {
  11.                        @Override
  12.                        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
  13.                            // 给前端返回一个json串.应用于前后端分离的项目.
  14.                            response.setContentType("application/json;charset=utf-8");
  15.                            Map<String, Object> map = new HashMap<>();
  16.                            map.put("code", 0); // 状态码
  17.                            map.put("msg", "认证成功");
  18.                            map.put("authentication", authentication);
  19.                            PrintWriter writer = response.getWriter();
  20.                            String json = new ObjectMapper().writeValueAsString(map);
  21.                            writer.print(json);
  22.                       }
  23.                   }).failureHandler(new AuthenticationFailureHandler() {
  24.                        @Override
  25.                        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
  26.                            // 给前端返回一个json串.应用于前后端分离的项目.
  27.                            response.setContentType("application/json;charset=utf-8");
  28.                            Map<String, Object> map = new HashMap<>();
  29.                            map.put("code", -1); // 状态码
  30.                            map.put("msg", "认证失败");
  31.                            map.put("exception", exception);
  32.                            PrintWriter writer = response.getWriter();
  33.                            String json = new ObjectMapper().writeValueAsString(map);
  34.                            writer.print(json);
  35.                       }
  36.                   }))
  37.            // .successForwardUrl("/success") // 默认的话,跳转到你在认证之前的请求.
  38.            // .defaultSuccessUrl("/success", true) // true,表示强制跳转到指定的url
  39.           .csrf(csrf -> csrf.disable())
  40.           .build();
  41. }

2.4 jwt

  1. <dependency>
  2.    <groupId>io.jsonwebtoken</groupId>
  3.    <artifactId>jjwt-api</artifactId>
  4.    <version>0.11.2</version>
  5. </dependency>
  6. <dependency>
  7.    <groupId>io.jsonwebtoken</groupId>
  8.    <artifactId>jjwt-impl</artifactId>
  9.    <version>0.11.2</version>
  10.    <scope>runtime</scope>
  11. </dependency>
  12. <dependency>
  13.    <groupId>io.jsonwebtoken</groupId>
  14.    <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
  15.    <version>0.11.2</version>
  16.    <scope>runtime</scope>
  17. </dependency>
  18. <!--解决高版本JDK问题-->
  19. <!--javax.xml.bind.DatatypeConverter错误-->
  20. <dependency>
  21.    <groupId>javax.xml.bind</groupId>
  22.    <artifactId>jaxb-api</artifactId>
  23.    <version>2.3.0</version>
  24. </dependency>
  25. <dependency>
  26.    <groupId>com.sun.xml.bind</groupId>
  27.    <artifactId>jaxb-impl</artifactId>
  28.    <version>2.3.0</version>
  29. </dependency>
  30. <dependency>
  31.    <groupId>com.sun.xml.bind</groupId>
  32.    <artifactId>jaxb-core</artifactId>
  33.    <version>2.3.0</version>
  34. </dependency>
  35. <dependency>
  36.    <groupId>javax.activation</groupId>
  37.    <artifactId>activation</artifactId>
  38.    <version>1.1.1</version>
  39. </dependency>

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

安全, 以json方式传输, 可以被验证和信任.本质还是一个字符串.定义规则,咱们可控的.


  1. package com.tingyi.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.spec.SecretKeySpec;
  7. import javax.xml.bind.DatatypeConverter;
  8. import java.security.Key;
  9. import java.util.Date;
  10. import java.util.UUID;
  11. /**
  12. * jwt工具类.
  13. */
  14. public class JwtUtil {
  15.    /**
  16.     * jwt过期时间
  17.     */
  18.    public static final Long EXP_TTL = 60 * 60 * 1000L;
  19.    /**
  20.     * jwt使用的密钥
  21.     */
  22.    public static final String JWT_KEY = "c3R1ZHkgaGFyZCBhbmQgbWFrZSBwcm9ncmVzcyBldmVyeSBkYXku";
  23.    /**
  24.     * 创建jwt字符串
  25.     * @param id id
  26.     * @param issuer 创建的作者
  27.     * @param subject 用户主体
  28.     * @param ttlMillis 过期时间, 毫秒值
  29.     * @return jwt字符串
  30.     */
  31.    public static String createJWT(String id, String issuer, String subject, long ttlMillis) {
  32.        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
  33.        long nowMillis = System.currentTimeMillis();
  34.        Date now = new Date(nowMillis);
  35.        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(JWT_KEY);
  36.        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
  37.        JwtBuilder builder = Jwts
  38.               .builder()
  39.               .setId(id)
  40.               .setIssuedAt(now)
  41.               .setSubject(subject)
  42.               .setIssuer(issuer)
  43.               .signWith(signingKey, signatureAlgorithm);
  44.        if (ttlMillis >= 0) {
  45.            long expMillis = nowMillis + ttlMillis;
  46.            Date exp = new Date(expMillis);
  47.            builder.setExpiration(exp);
  48.       }
  49.        return builder.compact();
  50.   }
  51.    /**
  52.     * 创建jwt字符串
  53.     * @param issuer 作者信息
  54.     * @param subject 用户主体信息
  55.     * @param ttlMillis 过期时间, 毫秒值
  56.     * @return jwt字符串
  57.     */
  58.    public static String createJwt(String issuer, String subject, long ttlMillis){
  59.        return createJWT(uuid(), issuer, subject, ttlMillis);
  60.   }
  61.    /**
  62.     * 创建jwt字符串
  63.     * @param issuer 作者信息
  64.     * @param subject 用户主体信息
  65.     * @return jwt字符串
  66.     */
  67.    public static String createJwt(String issuer, String subject){
  68.        return createJwt(issuer, subject, EXP_TTL);
  69.   }
  70.    /**
  71.     * 创建jwt字符串
  72.     * @param subject 用户主体
  73.     * @return jwt字符串
  74.     */
  75.    public static String createJwt(String subject){
  76.        return createJwt("laoren", subject, EXP_TTL);
  77.   }
  78.    /**
  79.     * uuid
  80.     * @return String
  81.     */
  82.    private static String uuid(){
  83.        return UUID.randomUUID().toString().replaceAll("-", "");
  84.   }
  85.    /**
  86.     * 解析jwt
  87.     * @param jwt jwt字符串
  88.     * @return Claims
  89.     */
  90.    public static Claims parseJWT(String jwt) {
  91.        Claims claims = Jwts.parserBuilder()
  92.               .setSigningKey(DatatypeConverter.parseBase64Binary(JWT_KEY))
  93.               .build()
  94.               .parseClaimsJws(jwt).getBody();
  95.        return claims;
  96.   }
  97.    public static void main(String[] args) {
  98.        // 生成一个jwt串.
  99.        String jwt = createJWT("1024", "tom", "jack", EXP_TTL);
  100.        System.out.println(jwt);
  101.        // 解析jwt串.
  102.        Claims claims = parseJWT(jwt);
  103.        Object subject = claims.get("subject");
  104.        System.out.println(subject);
  105.        System.out.println(claims);
  106.        System.out.println(claims.getSubject());
  107.        System.out.println(claims.getIssuedAt());
  108.        System.out.println(claims.getExpiration());
  109.   }
  110. }

2.5 官网认证流程

Form Login :: Spring Security

2.6 RBAC模型

RBAC(Role-Based Access Control),基于角色的访问控制。通过用户关联角色,角色关联权限,来间接的为用户赋予权限。

4.1. 创建表结构

下面是标准的RBAC模型关系表:

用户表

  1. -- 用户表
  2. CREATE TABLE `sys_user` (
  3. `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
  4. `name` varchar(50) NOT NULL COMMENT '用户名',
  5. `nick_name` varchar(150) DEFAULT NULL COMMENT '昵称',
  6. `password` varchar(100) DEFAULT NULL COMMENT '密码',
  7. `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  8. PRIMARY KEY (`id`),
  9. UNIQUE KEY `name` (`name`)
  10. ) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8 COMMENT='用户管理';

角色表

  1. -- 角色表
  2. CREATE TABLE `sys_role` (
  3. `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
  4. `name` varchar(100) DEFAULT NULL COMMENT '角色名称',
  5. `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  6. PRIMARY KEY (`id`)
  7. ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COMMENT='角色管理';

用户角色表

  1. -- 用户角色表
  2. CREATE TABLE `sys_user_role` (
  3. `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
  4. `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  5. `role_id` bigint(20) DEFAULT NULL COMMENT '角色ID',
  6. `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  7. PRIMARY KEY (`id`)
  8. ) ENGINE=InnoDB AUTO_INCREMENT=88 DEFAULT CHARSET=utf8 COMMENT='用户角色';

菜单表

  1. -- 菜单表
  2. CREATE TABLE `sys_menu` (
  3. `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
  4. `name` varchar(50) DEFAULT NULL COMMENT '菜单名称',
  5. `parent_id` bigint(20) DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
  6. `url` varchar(200) DEFAULT NULL COMMENT '菜单URL,类型:1.普通页面(如用户管理, /sys/user) 2.嵌套完整外部页面,以http(s)开头的链接 3.嵌套服务器页面,使用iframe:前缀+目标URL(如SQL监控, iframe:/druid/login.html, iframe:前缀会替换成服务器地址)',
  7. `perms` varchar(500) DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:sys:user:add,sys:user:edit)',
  8. `type` int(11) DEFAULT NULL COMMENT '类型   0:目录   1:菜单   2:按钮',
  9. `icon` varchar(50) DEFAULT NULL COMMENT '菜单图标',
  10. `order_num` int(11) DEFAULT NULL COMMENT '排序',
  11. `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  12. PRIMARY KEY (`id`)
  13. ) ENGINE=InnoDB AUTO_INCREMENT=57 DEFAULT CHARSET=utf8 COMMENT='菜单管理';

角色菜单表

  1. -- 角色菜单表
  2. CREATE TABLE `sys_role_menu` (
  3. `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
  4. `role_id` bigint(20) DEFAULT NULL COMMENT '角色ID',
  5. `menu_id` bigint(20) DEFAULT NULL COMMENT '菜单ID',
  6. `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  7. PRIMARY KEY (`id`)
  8. ) ENGINE=InnoDB AUTO_INCREMENT=623 DEFAULT CHARSET=utf8 COMMENT='角色菜单';

2.7 如何实现权限流程

按照: 认证的过程,其中实现了接口: UserDetailsService接口,之后,我们在重写的方法当中.

  1. @Override
  2. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  3.    LambdaQueryWrapper<TbUser> queryWrapper = new LambdaQueryWrapper<>();
  4.    queryWrapper.eq(TbUser::getUsername, username);
  5.    TbUser user = tbUserMapper.selectOne(queryWrapper); // 通过这个对象,获取密码.还有权限列表.
  6.    // 最终我们得把获取到的数据封装成UserDetails对象.交给spring security处理去.
  7.    // 这个集合当中,包含两个东西
  8.    // 角色列表, 应该通过用户id去数据库当中,通过多表查询给它查询出来. List<Role>
  9.    // 权限列表, 通过用户id, 去数据库当中,通过多表查询,权限查出来. List<Menu>
  10.    List<GrantedAuthority> grantedAuthorityList = Collections.emptyList(); // 权限列表.
  11.    
  12.    // 上一步完成之后,将封装好的List<GrantedAuthority>交给spring security,它会在我们需要验证权限的时候,就会给你验证了.
  13.    // 如何知道我需要进行权限验证,当类上或者方法上标记相关注解了.则表示我需要验证了.
  14.    // 当前登录的用户,是否有某个角色.
  15.    // 当前登录的用户, 是否拥有这个权限.
  16.    return new User(username, user.getPassword(), grantedAuthorityList);
  17. }

@Secured注解, 是否拥有某个角色,某些角色.

@PreAuthorize, 是否拥有某些权限.

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/码创造者/article/detail/989818
推荐阅读
相关标签
  

闽ICP备14008679号