首页 > 其他分享 >spring-security安全框架(超精细版附带流程讲解图)

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

时间:2024-07-03 16:29:30浏览次数:3  
标签:COMMENT return String 附带 spring 用户 import 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, 是否拥有某些权限.

 

标签:COMMENT,return,String,附带,spring,用户,import,security
From: https://blog.csdn.net/weixin_64296810/article/details/140077136

相关文章

  • springboot使用注解方式打印方法日志
    springboot使用注解方式打印方法日志,可以很方便的打印日志,通用性很强。耦合很低,很好。作为程序员的我不废话,咱们直接上代码先创建个注解@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceShowLog{/***日志描......
  • Spring的三种依赖注入的方式
    1、什么是依赖注入依赖注入(DependencyInjection,简称DI),是IOC的一种别称,用来减少对象间的依赖关系。提起依赖注入,就少不了IOC。IOC(InversionofControl,控制反转)是一种设计思想,它将原本在程序中手动创建对象的控制权,交由Spring框架来管理。IOC和DI,是同一个概念的不同角度描述。......
  • springboot的MultipartFile转File读取
    在SpringBoot中,处理文件上传时,MultipartFile接口被用来封装上传的文件信息。如果需要将MultipartFile转换为Java标准的File对象进行读取。以下是具体的操作流程:1.创建临时文件        首先,需要将接收到的MultipartFile对象转换为一个临时File对象。      ......
  • springboot-mybatis-db2
    工程pom.xml文件增加如下依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency>&......
  • 一文了解Spring Boot启动类SpringApplication
    本文分享自华为云社区《【SpringBoot源码学习】初识SpringApplication》,作者:Huazie。引言往期的博文,Huazie 围绕 SpringBoot 的核心功能,带大家从总整体上了解 SpringBoot 自动配置的原理以及自动配置核心组件的运作过程。这些内容大家需要重点关注,只有了解这些基础的......
  • Spring Cloud Gateway整合Knife4j 4.4.0实现微服务聚合文档(报错解决详细版)
    以前做过的都是单服务应用的文档,就算换到了微服务里做的实际上也是单服务(每个服务模块一个单独的文档,然后手动访问不同的端口去查找不同的模块文档,例如用户是3000端口,订单是3100端口,商品是3200端口)。这样的实现实际上挺蠢的,对前端伙伴很不友好,对自己测试也不友好,因此今天要说的......
  • Azkaban-3.84.4集群部署——安装篇(文章结尾附带网盘下载链接)
    目录Azkaban的集群规划Azkaban的集群部署第一步处理azkaban-db-3.84.4:第二步配置azkaban-exec:启动azkaban-exec:激活azkaban-exec:第三步配置WebServer(在bigdata1中)(1)修改azkaban.properties文件,修改的部分如下:(2)修改azkaban-users.xml文件,添加用户(用户密码可自行设......
  • springboot+手机商城网站-计算机毕业设计源码201029
    摘 要在信息飞速发展的今天,网络已成为人们重要的信息交流平台。手机店每天都有大量的手机商品需要通过网络发布,为此,本人开发了一个基于springboot手机商城网站。本系统采用跨平台的JAVA语言开发,利用springboot框架进行逻辑控制,MySQL数据库存储数据,最后Tomcat服务器完成发布......
  • Springboot整合Apollo
    一、Apollo作用随着程序功能的日益复杂,程序的配置日益增多:各种功能的开关、参数的配置、服务器的地址……对程序配置的期望值也越来越高:配置修改后实时生效,灰度发布,分环境、分集群管理配置,完善的权限、审核机制……在这样的大环境下,传统的通过配置文件、数据库等方式已经越来越......
  • Spring AI使用
    一、背景2024年5月30日发布了SpringAI1.0.0Milestone1,代表spring项目中引入包括LLM之类的AI类进入stable状态。jdk要求java17以上体现出AI项目的未来趋势,更对企业开发环境升级提出了要求。聊天模型:包括OpenAI、AzureOpenAI、AmazonBedrock、Cohere’sCommand、AI2......