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放到容器当中.

      /**
       * {@link Condition} for
       * {@link ConditionalOnDefaultWebSecurity @ConditionalOnDefaultWebSecurity}.
       *
       * @author Phillip Webb
       */
      class DefaultWebSecurityCondition extends AllNestedConditions {
      ​
          DefaultWebSecurityCondition() {
              super(ConfigurationPhase.REGISTER_BEAN);
          }
      ​
          @ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })
          static class Classes {
      ​
          }
      ​
          // 当IoC容器当中没有WebSecurityConfigurerAdapter.class, SecurityFilterChain.class 这两个类的对象
          // 则默认生效,否则默认配置不生效.
          @ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class, SecurityFilterChain.class })
          static class Beans {
      ​
          }
      ​
      }

    3. 默认的配置类

      SecurityProperties

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

    4. 对认证资源进行配置

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

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

二、security使用

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

public HttpSecurity authorizeRequests(
        Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry> authorizeRequestsCustomizer)
        throws Exception {
    ApplicationContext context = getContext();
    authorizeRequestsCustomizer
            .customize(getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context)).getRegistry());
    return HttpSecurity.this;
}

认证成功之后的处理:

public final T successHandler(AuthenticationSuccessHandler successHandler) {
    this.successHandler = successHandler;
    return getSelf();
}
package com.tingyi.configs;
​
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
​
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
​
/**
 * @author 听忆
 */
@Configuration
public class SecurityConfig {
​
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.authorizeRequests(authorize ->
                        authorize.mvcMatchers("/tom")
                                .permitAll()
                                .anyRequest()
                                .authenticated())
                .formLogin()
                // .successForwardUrl("/success") // 默认的话,跳转到你在认证之前的请求.
                // .defaultSuccessUrl("/success", true) // true,表示强制跳转到指定的url
                .successHandler(new AuthenticationSuccessHandler() { // security提供给我们的,认证成功之后的处理.我们可以在这里返回json给前端.



                    // 前后端分离项目使用的方式;
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        // 给前端返回一个json串.应用于前后端分离的项目.
                        Map<String, Object> map = new HashMap<>();
                        map.put("code", 0); // 状态码
                        map.put("msg", "认证成功");
                        map.put("authentication", authentication);
​
                        PrintWriter writer = response.getWriter();
                        String json = new ObjectMapper().writeValueAsString(map);
                        writer.print(json);
                    }
                }).and()
                .csrf(csrf -> csrf.disable())
                .build();
    }
}

认证流程:

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

    • UsernamePasswordAuthenticaionFilter

      • AbstractAuthenticationProcessingFilter


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

  • UserDetailsService

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

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

2.2 如何自定义认证

DaoAuthenticationProvider

protected void additionalAuthenticationChecks(UserDetails userDetails,
        UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    if (authentication.getCredentials() == null) {
        this.logger.debug("Failed to authenticate since no credentials provided");
        throw new BadCredentialsException(this.messages
                .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
    }
    String presentedPassword = authentication.getCredentials().toString();
    if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
        this.logger.debug("Failed to authenticate since password does not match stored value");
        throw new BadCredentialsException(this.messages
                .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
    }
}

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

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

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

​
​

package org.springframework.security.core.userdetails;
​
import java.io.Serializable;
import java.util.Collection;
​
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
​
/**
 * Provides core user information.
 *
 * <p>
 * Implementations are not used directly by Spring Security for security purposes. They
 * simply store user information which is later encapsulated into {@link Authentication}
 * objects. This allows non-security related user information (such as email addresses,
 * telephone numbers etc) to be stored in a convenient location.
 * <p>
 * Concrete implementations must take particular care to ensure the non-null contract
 * detailed for each method is enforced. See
 * {@link org.springframework.security.core.userdetails.User} for a reference
 * implementation (which you might like to extend or use in your code).
 *
 * @author Ben Alex
 * @see UserDetailsService
 * @see UserCache
 * 用户详细信息
 *  1. 用户名称
 *  2. 用户密码
 *  3. 用户的权限列表
 *      1. 角色信息
 *      2. 权限信息
 */
public interface UserDetails extends Serializable {
​
    /**
     * Returns the authorities granted to the user. Cannot return <code>null</code>.
     * @return the authorities, sorted by natural key (never <code>null</code>)
     * 权限列表
     */
    Collection<? extends GrantedAuthority> getAuthorities();
​
    /**
     * Returns the password used to authenticate the user.
     * @return the password
     * 获取用户密码
     */
    String getPassword();
​
    /**
     * Returns the username used to authenticate the user. Cannot return
     * <code>null</code>.
     * @return the username (never <code>null</code>)
     * 用户名称
     */
    String getUsername();
​
    /**
     * Indicates whether the user's account has expired. An expired account cannot be
     * authenticated.
     * @return <code>true</code> if the user's account is valid (ie non-expired),
     * <code>false</code> if no longer valid (ie expired)
     * 账号状态是否是过期的.
     */
    boolean isAccountNonExpired();
​
    /**
     * Indicates whether the user is locked or unlocked. A locked user cannot be
     * authenticated.
     * @return <code>true</code> if the user is not locked, <code>false</code> otherwise
     */
    boolean isAccountNonLocked();
​
    /**
     * Indicates whether the user's credentials (password) has expired. Expired
     * credentials prevent authentication.
     * @return <code>true</code> if the user's credentials are valid (ie non-expired),
     * <code>false</code> if no longer valid (ie expired)
     */
    boolean isCredentialsNonExpired();
​
    /**
     * Indicates whether the user is enabled or disabled. A disabled user cannot be
     * authenticated.
     * @return <code>true</code> if the user is enabled, <code>false</code> otherwise
     */
    boolean isEnabled();
​
}

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

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

重要的接口和实现类:

  • 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对象.

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

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

需要一个认证管理器:

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

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

直接调用认证方法:

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

  1. 更改配置文件

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

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http.authorizeRequests(authorize ->
                    authorize.mvcMatchers("/tom", "/login")
                            .permitAll()
                            .anyRequest()
                            .authenticated())
            // .formLogin() // 仅仅表示我使用表单验证, 但是配置用的都是默认的.
            .formLogin(form -> form.loginPage("/login.html").permitAll()
                    .successHandler(new AuthenticationSuccessHandler() {
                        @Override
                        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                            // 给前端返回一个json串.应用于前后端分离的项目.
                            response.setContentType("application/json;charset=utf-8");
                            Map<String, Object> map = new HashMap<>();
                            map.put("code", 0); // 状态码
                            map.put("msg", "认证成功");
                            map.put("authentication", authentication);
​
                            PrintWriter writer = response.getWriter();
                            String json = new ObjectMapper().writeValueAsString(map);
                            writer.print(json);
                        }
                    }).failureHandler(new AuthenticationFailureHandler() {
                        @Override
                        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                            // 给前端返回一个json串.应用于前后端分离的项目.
                            response.setContentType("application/json;charset=utf-8");
                            Map<String, Object> map = new HashMap<>();
                            map.put("code", -1); // 状态码
                            map.put("msg", "认证失败");
                            map.put("exception", exception);
​
                            PrintWriter writer = response.getWriter();
                            String json = new ObjectMapper().writeValueAsString(map);
                            writer.print(json);
                        }
                    }))
​
            // .successForwardUrl("/success") // 默认的话,跳转到你在认证之前的请求.
            // .defaultSuccessUrl("/success", true) // true,表示强制跳转到指定的url
            .csrf(csrf -> csrf.disable())
            .build();
}

2.4 jwt

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.2</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
    <version>0.11.2</version>
    <scope>runtime</scope>
</dependency>
​
<!--解决高版本JDK问题-->
<!--javax.xml.bind.DatatypeConverter错误-->
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.0</version>
</dependency>
<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-impl</artifactId>
    <version>2.3.0</version>
</dependency>
<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-core</artifactId>
    <version>2.3.0</version>
</dependency>
<dependency>
    <groupId>javax.activation</groupId>
    <artifactId>activation</artifactId>
    <version>1.1.1</version>
</dependency>

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

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


package com.tingyi.utils;
​
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
​
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
import java.util.UUID;
​
/**
 * jwt工具类.
 */
public class JwtUtil {
    /**
     * jwt过期时间
     */
    public static final Long EXP_TTL = 60 * 60 * 1000L;
​
    /**
     * jwt使用的密钥
     */
    public static final String JWT_KEY = "c3R1ZHkgaGFyZCBhbmQgbWFrZSBwcm9ncmVzcyBldmVyeSBkYXku";
​
    /**
     * 创建jwt字符串
     * @param id id
     * @param issuer 创建的作者
     * @param subject 用户主体
     * @param ttlMillis 过期时间, 毫秒值
     * @return jwt字符串
     */
    public static String createJWT(String id, String issuer, String subject, long ttlMillis) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
​
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(JWT_KEY);
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
​
        JwtBuilder builder = Jwts
                .builder()
                .setId(id)
                .setIssuedAt(now)
                .setSubject(subject)
                .setIssuer(issuer)
                .signWith(signingKey, signatureAlgorithm);
​
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp);
        }
        return builder.compact();
    }
​
    /**
     * 创建jwt字符串
     * @param issuer 作者信息
     * @param subject 用户主体信息
     * @param ttlMillis 过期时间, 毫秒值
     * @return jwt字符串
     */
    public static String createJwt(String issuer, String subject, long ttlMillis){
        return createJWT(uuid(), issuer, subject, ttlMillis);
    }
​
    /**
     * 创建jwt字符串
     * @param issuer 作者信息
     * @param subject 用户主体信息
     * @return jwt字符串
     */
    public static String createJwt(String issuer, String subject){
        return createJwt(issuer, subject, EXP_TTL);
    }
​
    /**
     * 创建jwt字符串
     * @param subject 用户主体
     * @return jwt字符串
     */
    public static String createJwt(String subject){
        return createJwt("laoren", subject, EXP_TTL);
    }
​
    /**
     * uuid
     * @return String
     */
    private static String uuid(){
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
​
    /**
     * 解析jwt
     * @param jwt  jwt字符串
     * @return  Claims
     */
    public static Claims parseJWT(String jwt) {
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(DatatypeConverter.parseBase64Binary(JWT_KEY))
                .build()
                .parseClaimsJws(jwt).getBody();
​
        return claims;
    }
​
    public static void main(String[] args) {
        // 生成一个jwt串.
        String jwt = createJWT("1024", "tom", "jack", EXP_TTL);
        System.out.println(jwt);
        // 解析jwt串.
        Claims claims = parseJWT(jwt);
        Object subject = claims.get("subject");
        System.out.println(subject);
        System.out.println(claims);
        System.out.println(claims.getSubject());
        System.out.println(claims.getIssuedAt());
        System.out.println(claims.getExpiration());
    }
}

2.5 官网认证流程

Form Login :: Spring Security

2.6 RBAC模型

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

4.1. 创建表结构

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

用户表

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

角色表

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

用户角色表

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

菜单表

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

角色菜单表

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

2.7 如何实现权限流程

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

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

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

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/758376.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【自然资源】国家历史文化名城你知道多少?

【自然资源】国家历史文化名城你知道多少&#xff1f; 中国五千年的历史孕育出了一些因深厚的文化底蕴和发生过重大历史事件而青史留名的城市。根据《中华人民共和国文物保护法》&#xff0c;“历史文化名城”是指保存文物特别丰富&#xff0c;具有重大历史文化价值和革命意义…

小红书多账号管理平台哪个好用?可以快速监测多个小红书账号的数据吗?

随着品牌营销战线的不断扩展&#xff0c;小红书已经成为企业和个人品牌竞相展示的舞台。但是&#xff0c;随之而来的多账号管理问题也让众多运营者头疼不已。一个优秀的多账号管理平台&#xff0c;能让你事半功倍&#xff0c;轻松监控和分析账号数据。 如今&#xff0c;市面上出…

昇思25天学习打卡营第12天 | ResNet50图像分类

内容介绍&#xff1a; ResNet50网络是2015年由微软实验室的何恺明提出&#xff0c;获得ILSVRC2015图像分类竞赛第一名。在ResNet网络提出之前&#xff0c;传统的卷积神经网络都是将一系列的卷积层和池化层堆叠得到的&#xff0c;但当网络堆叠到一定深度时&#xff0c;就会出现…

NPOI入门指南:轻松操作Excel文件的.NET库

目录 引言 一、NPOI概述 二、NPOI的主要用途 三、安装NPOI库 四、NPOI基本使用 六、性能优化和内存管理 七、常见问题与解决方案 八、结论 附录 引言 Excel文件作为数据处理的重要工具&#xff0c;广泛应用于各种场景。然而&#xff0c;在没有安装Microsoft Office的…

JavaScript面试宝典

栈和堆的区别 栈(stack)&#xff1a; 栈是内存的简称&#xff0c;栈是自动分配相对固定大小的内存空间&#xff0c;并由系统自动释放&#xff0c;栈数据结构遵循FILO&#xff08;first in last out&#xff09;先进后出的原则。 堆(heap)&#xff1a; 是堆内存的简称&#xff…

【杂说咋说】中国历史上最古老的十大建筑​,看看你都去过几个?

【杂说咋说】中国历史上最古老的十大建筑​&#xff0c;看看你都去过几个&#xff1f; 中国作为世界四大文明古国之一&#xff0c;历史文化源远流长。在几千年的历史变迁中&#xff0c;中华先祖在神州大地上留下了无数遗迹&#xff0c;其中包括很多古建筑。本期就来介绍一下中…

Pinia详解

文章目录 简介特点用法1. 安装Pinia2. 注册Pinia Store3. 创建Pinia Store4. 使用Pinia Store 区别 Vuex详解 Pinia是一个基于Vue 3的状态管理库&#xff0c;专为Vue 3设计。它提供了一种简单、直观且可扩展的方式来组织和访问应用程序的状态。Pinia的设计灵感来源于Vuex&#…

【proteus经典实战】16X192点阵程序

一、简介 6X192点阵程序通常用于表示高分辨率图像或文字&#xff0c;其中16X表示像素阵列的宽度&#xff0c;192表示每个像素阵列中的点阵数&#xff0c;16X192点阵程序需要一定的编程知识和技能才能编写和调试&#xff0c;同时还需要考虑硬件设备的兼容性和性能等因素。 初始…

玩游戏就能学习亚马逊云科技AWS技术并通过热门技术认证考试??

亚马逊AWS限时活动&#xff0c;玩免费游戏Cloud Quest Practitioner送AWS云从业证书考试25%折扣券(价值171元)&#xff0c;玩游戏的同时还能学知识一举两得。Cloud Quest是AWS出的一款3D角色扮演游戏/虚拟城市建造形式的实验课程(游戏画面有点像天际线)&#xff0c;大家通过完成…

FPV穿越机集群控制技术详解

随着无人机技术的不断发展&#xff0c;FPV&#xff08;First Person View&#xff0c;第一人称视角&#xff09;穿越机在娱乐、航拍、搜索与救援等领域的应用日益广泛。FPV穿越机集群控制技术则是这一领域的热点研究方向&#xff0c;旨在通过协同控制多个穿越机实现更高效、更复…

Efficient Unified Demosaicing for Bayer and Non-Bayer Patterned Image Sensors

这篇文章是 2023 ICCV 的一篇文章&#xff0c;主要介绍一套统一的去马赛克的算法框架的 由于手机 Camera 上 CMOS 的单个 pixel size 比较小&#xff0c;所以现在很多手机的 Camera CMOS 会采用一些独特的非 Bayer 模式的 CFA (Quad, Nona 以及 Q X Q) 等&#xff0c;这类非 B…

【Linux】已解决:Ubuntu虚拟机安装Java/JDK

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项结论 已解决&#xff1a;Ubuntu虚拟机安装Java/JDK 一、分析问题背景 在Ubuntu虚拟机上安装Java开发工具包&#xff08;JDK&#xff09;是许多开发者的常见任务。然而&#xff0c;在…

STM32音频应用开发:DMA与定时器的高效协作

摘要: 本文章将深入浅出地介绍如何使用STM32单片机实现音频播放功能。文章将从音频基础知识入手&#xff0c;逐步讲解音频解码、DAC转换、音频放大等关键环节&#xff0c;并结合STM32 HAL库给出具体的代码实现和电路设计方案。最后&#xff0c;我们将通过一个实例演示如何播放W…

! Warning: `flutter` on your path resolves to

目录 项目场景&#xff1a; 问题描述 原因分析&#xff1a; 解决方案&#xff1a; 1. 检查并更新.bash_profile或.zshrc文件 2.添加Flutter路径到环境变量 3. 加载配置文件 4.验证Flutter路径 5.重新启动终端 项目场景&#xff1a; 今天重新安装了AndroidStudio,并配置…

zdppy_api+vue3实现前后端分离的登录功能

实现思路 1、准备zdppy的开发环境 2、使用amauth提供的低代码接口&#xff0c;直接生成login登录接口 3、使用之前开发的登录模板渲染登录界面 4、给登录按钮绑定点击事件 5、给用户名和密码的输入框双向绑定数据 6、使用axios在登录按钮点击的时候&#xff0c;携带用户数据发…

Linux部署wordpress站点

先安装宝塔面板 yum install -y wget && wget -O install.sh https://download.bt.cn/install/install_6.0.sh && sh install.sh ed8484bec 因为wordpress需要php&#xff0c;mysql&#xff0c;apache &#xff0c;httpd环境 参考&#xff1a;Linux 安装宝塔…

【学习】使用PyTorch训练与评估自己的ResNet网络教程

参考&#xff1a;保姆级使用PyTorch训练与评估自己的ResNet网络教程_训练自己的图像分类网络resnet101 pytorch-CSDN博客 项目地址&#xff1a;GitHub - Fafa-DL/Awesome-Backbones: Integrate deep learning models for image classification | Backbone learning/comparison…

HBase Shell命令详解

HBase Shell命令 一、 命名空间 命名空间是 HBase 中用于组织表的一种逻辑容器&#xff0c;类似于文件系统中的文件夹。 Namespace允许用户在 HBase 中更好地管理和组织表&#xff0c;以及提供了隔离和命名约定。 1. 创建命名空间 命令&#xff1a; create_namespace name…

【scrapy】1.scrapy爬虫入门

一、scrapy爬虫框架 Scrapy 框架是一个基于Twisted的一个异步处理爬虫框架&#xff0c;应用范围非常的广泛&#xff0c;常用于数据采集、网络监测&#xff0c;以及自动化测试等。 scrapy框架包括5个主要的组件&#xff1a; Scheduler&#xff1a;事件调度器&#xff0c;它负…

机器学习引领教育革命:智能教育的新时代

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀目录 &#x1f4d2;1. 引言&#x1f4d9;2. 机器学习在教育中的应用&#x1f31e;个性化学习&#x1f319;评估与反馈的智能化⭐教学资源的优…