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 {
​@Beanpublic 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给前端.// 前后端分离项目使用的方式;@Overridepublic 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;}
​@Overridepublic 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();
}

直接调用认证方法:

@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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, 是否拥有某些权限.

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法力扣刷题 二十五【28.找出字符串中第一个匹配项的下标】

前言 字符串篇&#xff0c;继续。 记录 二十五【28.找出字符串中第一个匹配项的下标】 一、题目阅读 给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不…

C语言中的基础指针操作

在C语言中&#xff0c;指针是一个非常重要的概念&#xff0c;它提供了直接访问内存地址的能力。指针变量用于存储内存地址&#xff0c;而不是数据值&#xff0c;在某种意义上和门牌号具有相似含义&#xff1a;指针是一个变量&#xff0c;其存储的是另一个变量的内存地址&#x…

java之动态代理

1 代理模式 代理模式提供了对目标对象额外的访问方式&#xff0c;即通过代理对象访问目标对象&#xff0c;这样可以在不修改原目标对象的前提下&#xff0c;提供额外的功能操作&#xff0c;扩展目标对象的功能。简言之&#xff0c;代理模式就是设置一个中间代理来控制访问原目标…

网络io与select,poll,epoll

前言 网络 IO&#xff0c;会涉及到两个系统对象&#xff0c;一个是用户空间调用 IO 的进程或者线程&#xff0c;另一个是内核空间的内核系统&#xff0c;比如发生 IO 操作 read 时&#xff0c;它会经历两个阶段&#xff1a; 1. 等待数据准备就绪 2. 将数据从内核拷贝到进程或…

网络编程常见问题

1、TCP状态迁移图 2、TCP三次握手过程 2.1、握手流程 1、TCP服务器进程先创建传输控制块TCB&#xff0c;时刻准备接受客户进程的连接请求&#xff0c;此时服务器就进入了LISTEN&#xff08;监听&#xff09;状态&#xff1b; 2、TCP客户进程也是先创建传输控制块TCB&#xff…

改进经验模态分解方法-通过迭代方式(IMF振幅加权频率,Python)

一种新颖的改进经验模态分解方法-通过迭代方式&#xff08;IMF振幅加权频率&#xff09;有效缓解了模态混叠缺陷&#xff0c;以后慢慢讲&#xff0c;先占坑。 import numpy as np import matplotlib.pyplot as plt import os import seaborn as sns from scipy import stats i…

C语言图书管理系统控制台程序

程序示例精选 C语言图书管理系统控制台程序 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《C语言图书管理系统控制台程序》编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读…

加密与安全_三种方式实现基于国密非对称加密算法的加解密和签名验签

文章目录 国际算法基础概念常见的加密算法及分类签名和验签基础概念常见的签名算法应用场景 国密算法对称加密&#xff08;DES/AES⇒SM4&#xff09;非对称加密&#xff08;RSA/ECC⇒SM2&#xff09;散列(摘要/哈希)算法&#xff08;MD5/SHA⇒SM3&#xff09; Code方式一 使用B…

智慧园区综合平台解决方案PPT(75页)

## 智慧园区的理解 ### 从园区1.0到园区4.0的演进 1. 园区1.0&#xff1a;以土地经营为主&#xff0c;成本驱动&#xff0c;提供基本服务。 2. 园区2.0&#xff1a;服务驱动&#xff0c;关注企业成长&#xff0c;提供增值服务。 3. 园区3.0&#xff1a;智慧型园区&#xff…

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

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

STC89C52RC单片机设计的FM收音机+自动搜台+存储电台(程序+原理图+PCB)

资料下载地址&#xff1a;STC89C52RC单片机设计的FM收音机自动搜台存储电台&#xff08;程序原理图PCB) 1、实物图 2、部分程序 #include <reg52.h> #include "tea5767.h" #include "delay.h" #include "lcd1602.h" //K1:上一台 K2:下一…

mac电脑游戏推荐:NBA 2K24 街机版下载

NBA 2K24 街机版是一款由2K Sports开发并发行的篮球游戏&#xff0c;属于著名的NBA 2K系列。这款游戏为玩家提供了与NBA联赛中真实球员和球队互动的机会&#xff0c;体验篮球比赛的激情与紧张。街机版的NBA 2K24通常会在游戏厅、商场等公共场所设置&#xff0c;供玩家投币游玩。…

c++重载(运算符)

1&#xff09;C入门级小知识&#xff0c;分享给将要学习或者正在学习C开发的同学。 2&#xff09;内容属于原创&#xff0c;若转载&#xff0c;请说明出处。 3&#xff09;提供相关问题有偿答疑和支持。 对于系统的所有操作符&#xff0c;一般情况下&#xff0c;只支持基本数…

AWTK 用 icon_at 属性设置图标位置

1. style 在 style 文件中通过 icon_at 属性设置图标位置。 <style name"right_bottom" icon_at"right_bottom"><normal icon"unchecked_right_bottom" /><pressed icon"unchecked_right_bottom" /><over i…

redis实战-短信登录

基于session的登录流程 session的登录流程图 1. 发送验证码 用户在提交手机号后&#xff0c;会校验手机号是否合法&#xff0c;如果不合法&#xff0c;则要求用户重新输入手机号 如果手机号合法&#xff0c;后台此时生成对应的验证码&#xff0c;同时将验证码进行保存&#x…

第一节:如何开发第一个spring boot3.x项目(自学Spring boot 3.x的第一天)

大家好&#xff0c;我是网创有方&#xff0c;从今天开始&#xff0c;我会记录每篇我自学spring boot3.x的经验。只要我不偷懒&#xff0c;学完应该很快&#xff0c;哈哈&#xff0c;更新速度尽可能快&#xff0c;想和大佬们一块讨论&#xff0c;如果需要讨论的欢迎一起评论区留…

Pytorch实战(二)

文章目录 前言一、LeNet5原理1.1LeNet5网络结构1.2LeNet网络参数1.3LeNet5网络总结 二、AlexNext2.1AlexNet网络结构2.2AlexNet网络参数2.3Dropout操作2.4PCA图像增强2.5LRN正则化2.6AlexNet总结 三、实战3.1LeNet5模型搭建3.2模型训练 前言 参考原视频&#xff1a;哔哩哔哩。 …

【后端面试题】【中间件】【NoSQL】ElasticSearch面试基本思路和高可用方案(限流、消息队列、协调节点、双集群)

基本思路 业务开发面试Elasticsearch的时候基本问的是基础知识以及倒排索引。 Elasticsearch最基本的可用性保障就是分片&#xff0c;而且是主从分片&#xff0c;所以遇到Elasticsearch如何做到高可用这个问题的时候&#xff0c;首先要提到这一点。 Elasticsearch高可用的核心…

手机屏幕贴合项目(ni视觉如何找矩形的角坐标)

首先&#xff0c;我们存储了cg和dito感兴趣八个角图像的模板&#xff0c;用来匹配位置。 cover指的是cg的四个角模板&#xff0c;lcm是dito四个角匹配模板。 其次&#xff0c;我们采集的8副图像&#xff08;m_DlgCCDViewArr[2][4]&#xff09;中一定包含匹配模板的特征。 好&…

Json与Java类

简介 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;易于人阅读和编写&#xff0c;同时也易于机器解析和生成。JSON数据由键值对构成&#xff0c;并以易于阅读的文本形式展现&#xff0c;支持数组、对象、字符串、数字、布尔值…