java必知必会_Java必知必会-Spring Security

2023-11-12

一、Spring Security介绍

1、框架介绍

Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization****)两个部分。

(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。

(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

Spring Security其实就是用filter,多请求的路径进行过滤。

(1)如果是基于Session,那么Spring-security会对cookie里的sessionid进行解析,找到服务器存储的sesion信息,然后判断当前用户是否符合请求的要求。

(2)如果是token,则是解析出token,然后将当前请求加入到Spring-security管理的权限信息中去

2、认证与授权实现思路

如果系统的模块众多,每个模块都需要授权与认证,所以我们选择基于token的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为key,权限列表为value的形式存入redis缓存中,根据用户名相关信息生成token返回,浏览器将token记录到cookie中,每次调用api接口都默认将cookie中的token携带到header请求头中,Spring-security解析header头从而获取token信息,再解析token获取当前用户名,根据用户名就可以从redis中获取权限列表,这样Spring-security就能够判断当前请求是否有权限访问

Spring Security 支持两种不同的认证方式:

可以通过 form 表单来认证

可以通过 HttpBasic 来认证

二、整合Spring Security

1、在common下创建spring_security模块

423034c7-c21a-4555-8380-393ac7ebca4d.png

2、在spring_security引入相关依赖

org.springframework.boot

spring-boot-starter-security

io.jsonwebtoken

jjwt

\3、在service_acl引入**spring_security**依赖*

com.atguigu

spring_security

0.0.1-SNAPSHOT

0、代码结构说明:

6aafe337-8a5d-440c-b93d-ff60d4f9cef6.png

4、创建spring security核心配置类TokenWebSecurityConfig

Spring Security的核心配置就是继承WebSecurityConfigurerAdapter并注解@EnableWebSecurity的配置。

这个配置指明了用户名密码的处理方式、请求路径的开合、登录登出控制等和安全相关的配置

@Configuration

@EnableWebSecurity

@EnableGlobalMethodSecurity(prePostEnabled = true)

public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {

private UserDetailsService userDetailsService;

private TokenManager tokenManager;

private DefaultPasswordEncoder defaultPasswordEncoder;

private RedisTemplate redisTemplate;

@Autowired

public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,

TokenManager tokenManager, RedisTemplate redisTemplate) {

this.userDetailsService = userDetailsService;

this.defaultPasswordEncoder = defaultPasswordEncoder;

this.tokenManager = tokenManager;

this.redisTemplate = redisTemplate;

}

/**

* 配置设置

* @param http

* @throws Exception

*/

@Override

protected void configure(HttpSecurity http) throws Exception {

http.exceptionHandling()

.authenticationEntryPoint(new UnauthorizedEntryPoint())

.and().csrf().disable()

.authorizeRequests()

.anyRequest().authenticated()

.and().logout().logoutUrl("/admin/acl/index/logout")

.addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and()

.addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))

.addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();

}

/**

* 密码处理

* @param auth

* @throws Exception

*/

@Override

public void configure(AuthenticationManagerBuilder auth) throws Exception {

auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);

}

/**

* 配置哪些请求不拦截

* @param web

* @throws Exception

*/

@Override

public void configure(WebSecurity web) throws Exception {

web.ignoring().antMatchers("/api/**",

"/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"

);

}

}

5、创建认证授权相关的工具类

75ccbeef-10c1-45fd-8f68-b598019832b1.png

(1)DefaultPasswordEncoder:密码处理的方法

package com.atguigu.serurity.security;

import com.atguigu.commonutils.utils.MD5;

import org.springframework.security.crypto.password.PasswordEncoder;

import org.springframework.stereotype.Component;

/**

*

* 密码的处理方法类型

*

*/

@Component

public class DefaultPasswordEncoder implements PasswordEncoder {

public DefaultPasswordEncoder() {

this(-1);

}

/**

* @param strength

* the log rounds to use, between 4 and 31

*/

public DefaultPasswordEncoder(int strength) {

}

public String encode(CharSequence rawPassword) {

return MD5.encrypt(rawPassword.toString()); // 密码加密

}

public boolean matches(CharSequence rawPassword, String encodedPassword) {

return encodedPassword.equals(MD5.encrypt(rawPassword.toString())); // 密码认证

}

}

(2)TokenManager:token操作的工具类

/**

*

* token管理

*

*/

@Component

public class TokenManager {

private long tokenExpiration = 24*60*60*1000;

private String tokenSignKey = "123456";

// 根据用户名创建token值

public String createToken(String username) {

String token = Jwts.builder().setSubject(username)

.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))

.signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();

return token;

}

// 获取到请求头的token,并解析获取到对应用户

public String getUserFromToken(String token) {

String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();

return user;

}

public void removeToken(String token) {

//jwttoken无需删除,客户端扔掉即可。

}

}

(3)TokenLogoutHandler:退出实现

/**

*

* 登出业务逻辑类

*

*/

public class TokenLogoutHandler implements LogoutHandler {

private TokenManager tokenManager;

private RedisTemplate redisTemplate;

public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {

this.tokenManager = tokenManager;

this.redisTemplate = redisTemplate;

}

// 退出登录处理

@Override

public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {

String token = request.getHeader("token");

if (token != null) {

tokenManager.removeToken(token);

//清空当前用户缓存中的权限数据

String userName = tokenManager.getUserFromToken(token);

redisTemplate.delete(userName);

}

ResponseUtil.out(response, R.ok());

}

}

(4)UnauthorizedEntryPoint:未授权统一处理

import com.atguigu.commonutils.R;

import com.atguigu.commonutils.utils.ResponseUtil;

import org.springframework.security.core.AuthenticationException;

import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

/**

*

* 未授权的统一处理方式

*

*/

public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {

@Override

public void commence(HttpServletRequest request, HttpServletResponse response,

AuthenticationException authException) throws IOException, ServletException {

ResponseUtil.out(response, R.error());

}

}

6、创建认证授权实体类

76236594-2238-4d28-a15e-7cf743bb53e2.png

(1)SecutityUser

/**

*

* 安全认证用户详情信息

*

*/

@Data

@Slf4j

public class SecurityUser implements UserDetails {

//当前登录用户

private transient User currentUserInfo; // 禁止序列化user

//当前权限

private List permissionValueList;

public SecurityUser() {

}

public SecurityUser(User user) {

if (user != null) {

this.currentUserInfo = user;

}

}

@Override

public Collection extends GrantedAuthority> getAuthorities() {

Collection authorities = new ArrayList<>();

for(String permissionValue : permissionValueList) {

if(StringUtils.isEmpty(permissionValue)) continue;

SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);

authorities.add(authority);

}

return authorities; // 返回当前用户权限信息

}

@Override

public String getPassword() {

return currentUserInfo.getPassword();

}

@Override

public String getUsername() {

return currentUserInfo.getUsername();

}

@Override

public boolean isAccountNonExpired() {

return true;

}

@Override

public boolean isAccountNonLocked() {

return true;

}

@Override

public boolean isCredentialsNonExpired() {

return true;

}

@Override

public boolean isEnabled() {

return true;

}

}

(2)User

/**

*

* 用户实体类

*

*/

@Data

@ApiModel(description = "用户实体类")

public class User implements Serializable {

private static final long serialVersionUID = 1L;

@ApiModelProperty(value = "微信openid")

private String username;

@ApiModelProperty(value = "密码")

private String password;

@ApiModelProperty(value = "昵称")

private String nickName;

@ApiModelProperty(value = "用户头像")

private String salt;

@ApiModelProperty(value = "用户签名")

private String token;

}

7、创建认证和授权的filter

bee4d171-befe-4b08-a792-5f1d50a8642b.png

(1)TokenLoginFilter:认证的filter

/**

*

* 登录过滤器,继承UsernamePasswordAuthenticationFilter,对用户名密码进行登录校验

*

*/

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

private AuthenticationManager authenticationManager;

private TokenManager tokenManager;

private RedisTemplate redisTemplate;

public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {

this.authenticationManager = authenticationManager;

this.tokenManager = tokenManager;

this.redisTemplate = redisTemplate;

this.setPostOnly(false);

this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));

}

@Override

public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)

throws AuthenticationException {

try {

User user = new ObjectMapper().readValue(req.getInputStream(), User.class);

return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));

} catch (IOException e) {

throw new RuntimeException(e);

}

}

/**

* 登录成功

* @param req

* @param res

* @param chain

* @param auth

* @throws IOException

* @throws ServletException

*/

@Override

protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,

Authentication auth) throws IOException, ServletException {

SecurityUser user = (SecurityUser) auth.getPrincipal();

String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());

redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());

ResponseUtil.out(res, R.ok().data("token", token));

}

/**

* 登录失败

* @param request

* @param response

* @param e

* @throws IOException

* @throws ServletException

*/

@Override

protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,

AuthenticationException e) throws IOException, ServletException {

ResponseUtil.out(response, R.error());

}

}

(2)TokenAuthenticationFilter:

/**

*

* 访问过滤器

*

*/

public class TokenAuthenticationFilter extends BasicAuthenticationFilter {

private TokenManager tokenManager;

private RedisTemplate redisTemplate;

public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) {

super(authManager);

this.tokenManager = tokenManager;

this.redisTemplate = redisTemplate;

}

@Override

protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)

throws IOException, ServletException {

logger.info("================="+req.getRequestURI());

if(req.getRequestURI().indexOf("admin") == -1) {

chain.doFilter(req, res);

return;

}

UsernamePasswordAuthenticationToken authentication = null;

try {

authentication = getAuthentication(req);

} catch (Exception e) {

ResponseUtil.out(res, R.error());

}

if (authentication != null) {

SecurityContextHolder.getContext().setAuthentication(authentication);

} else {

ResponseUtil.out(res, R.error());

}

chain.doFilter(req, res);

}

private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {

// token置于header里

String token = request.getHeader("token");

if (token != null && !"".equals(token.trim())) {

String userName = tokenManager.getUserFromToken(token);

List permissionValueList = (List) redisTemplate.opsForValue().get(userName);

Collection authorities = new ArrayList<>();

for(String permissionValue : permissionValueList) {

if(StringUtils.isEmpty(permissionValue)) continue;

SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);

authorities.add(authority);

}

if (!StringUtils.isEmpty(userName)) {

return new UsernamePasswordAuthenticationToken(userName, token, authorities);

}

return null;

}

return null;

}

}

底层原理

核心组件

SecurityContextHolder:提供对SecurityContext的访问

SecurityContext,:持有Authentication对象和其他可能需要的信息

AuthenticationManager 其中可以包含多个AuthenticationProvider

ProviderManager对象为AuthenticationManager接口的实现类

AuthenticationProvider 主要用来进行认证操作的类 调用其中的authenticate()方法去进行认证操作

Authentication:Spring Security方式的认证主体

GrantedAuthority:对认证主题的应用层面的授权,含当前用户的权限信息,通常使用角色表示

UserDetails:构建Authentication对象必须的信息,可以自定义,可能需要访问DB得到

UserDetailsService:通过username构建UserDetails对象,通过loadUserByUsername根据userName获取UserDetail对象 (可以在这里基于自身业务进行自定义的实现 如通过数据库,xml,缓存获取等)

主要过滤器

想要对对Web资源进行保护,最好的办法莫过于Filter,要想对方法调用进行保护,最好的办法莫过于AOP。所以springSecurity在我们进行用户认证以及授予权限的时候,通过各种各样的拦截器来控制权限的访问,从而实现安全。

​ WebAsyncManagerIntegrationFilter

​ SecurityContextPersistenceFilter

​ HeaderWriterFilter

​ CorsFilter

​ LogoutFilter

​ RequestCacheAwareFilter

​ SecurityContextHolderAwareRequestFilter

​ AnonymousAuthenticationFilter

​ SessionManagementFilter

​ ExceptionTranslationFilter

​ FilterSecurityInterceptor

​ UsernamePasswordAuthenticationFilter

​ BasicAuthenticationFilter

自定义安全机制的加载机制

自定义了一个springSecurity安全框架的配置类 继承WebSecurityConfigurerAdapter,重写其中的方法configure

实现该类后,在web容器启动的过程中该类实例对象会被WebSecurityConfiguration类处理。

继承WebSecurityConfigurerAdapter

@Configuration

public class SpringSecurityConfig extends WebSecurityConfigurerAdapter{

//重写了其中的configure()方法设置了不同url的不同访问权限

@Override

protected void configure(HttpSecurity http) throws Exception {

http.csrf().disable()

.authorizeRequests()

.antMatchers("/home", "/about","/img/*").permitAll()

.antMatchers("/admin/**","/upload/**").hasAnyRole("ADMIN")

.antMatchers("/order/**").hasAnyRole("USER","ADMIN")

.antMatchers("/room/**").hasAnyRole("USER","ADMIN")

.anyRequest().authenticated()

.and()

.formLogin()

.loginPage("/login")

.permitAll()

.and()

.logout()

.permitAll()

.and()

.exceptionHandling().accessDeniedHandler(accessDeniedHandler);

}

}

WebSecurityConfiguration

@Configuration

public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {

private WebSecurity webSecurity;

private Boolean debugEnabled;

private List> webSecurityConfigurers;

private ClassLoader beanClassLoader;

...省略部分代码

@Bean(

name = {"springSecurityFilterChain"}

)

public Filter springSecurityFilterChain() throws Exception {

boolean hasConfigurers = this.webSecurityConfigurers != null

&& !this.webSecurityConfigurers.isEmpty();

if(!hasConfigurers) {

WebSecurityConfigurerAdapter adapter = (WebSecurityConfigurerAdapter)

this.objectObjectPostProcessor

.postProcess(new WebSecurityConfigurerAdapter() {

});

this.webSecurity.apply(adapter);

}

return (Filter)this.webSecurity.build();

}

/*1、先执行该方法将我们自定义springSecurity配置实例

(可能还有系统默认的有关安全的配置实例 ) 配置实例中含有我们自定义业务的权限控制配置信息

放入到该对象的list数组中webSecurityConfigurers中

使用@Value注解来将实例对象作为形参注入

*/

@Autowired(

required = false

)

public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor

objectPostProcessor,

@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}")

List> webSecurityConfigurers)

throws Exception {

//创建一个webSecurity对象

this.webSecurity = (WebSecurity)objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));

if(this.debugEnabled != null) {

this.webSecurity.debug(this.debugEnabled.booleanValue());

}

//对所有配置类的实例进行排序

Collections.sort(webSecurityConfigurers, WebSecurityConfiguration.AnnotationAwareOrderComparator.INSTANCE);

Integer previousOrder = null;

Object previousConfig = null;

//迭代所有配置类的实例 判断其order必须唯一

Iterator var5;

SecurityConfigurer config;

for(var5 = webSecurityConfigurers.iterator(); var5.hasNext(); previousConfig = config) {

config = (SecurityConfigurer)var5.next();

Integer order = Integer.valueOf(WebSecurityConfiguration.AnnotationAwareOrderComparator.lookupOrder(config));

if(previousOrder != null && previousOrder.equals(order)) {

throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of " + order + " was already used on " + previousConfig + ", so it cannot be used on " + config + " too.");

}

previousOrder = order;

}

//将所有的配置实例添加到创建的webSecutity对象中

var5 = webSecurityConfigurers.iterator();

while(var5.hasNext()) {

config = (SecurityConfigurer)var5.next();

this.webSecurity.apply(config);

}

//将webSercurityConfigures 实例放入该对象的webSecurityConfigurers属性中

this.webSecurityConfigurers = webSecurityConfigurers;

}

}

SecurityContextHolder

这是一个工具类,只提供一些静态方法。这个工具类的目的是用来保存应用程序中当前使用人的安全上下文。

作用:保留系统当前的安全上下文细节,其中就包括当前使用系统的用户的信息。

(1)单机系统,即应用从开启到关闭的整个生命周期只有一个用户在使用。由于整个应用只需要保存一个SecurityContext(安全上下文即可)

(2)多用户系统,比如典型的Web系统,整个生命周期可能同时有多个用户在使用。这时候应用需要保存多个SecurityContext(安全上下文),需要利用ThreadLocal进行保存,每个线程都可以利用ThreadLocal获取其自己的SecurityContext,及安全上下文。

缺省工作模式 MODE_THREADLOCAL

一个应用同时可能有多个使用者,每个使用者对应不同的安全上下文。缺省情况下,SecurityContextHolder使用了ThreadLocal机制来保存每个使用者的安全上下文。这意味着,只要针对某个使用者的逻辑执行都是在同一个线程中进行,即使不在各个方法之间以参数的形式传递其安全上下文,各个方法也能通过SecurityContextHolder工具获取到该安全上下文。只要在处理完当前使用者的请求之后注意清除ThreadLocal中的安全上下文,这种使用ThreadLocal的方式是很安全的。当然在Spring Security中,这些工作已经被Spring Security自动处理,开发人员不用担心这一点。

SecurityContextHolder基于ThreadLocal的工作方式天然很适合Servlet Web应用,因为缺省情况下根据Servlet规范,一个Servlet request的处理不管经历了多少个Filter,自始至终都由同一个线程来完成。

注意 : 这里讲的是一个Servlet request的处理不管经历了多少个Filter,自始至终都由同一个线程来完成;而对于同一个使用者的不同Servlet request,它们在服务端被处理时,使用的可不一定是同一个线程(存在由同一个线程处理的可能性但不确保)。

其他工作模式

有一些应用并不适合使用ThreadLocal模式,那么还能不能使用SecurityContextHolder了呢?答案是可以的。SecurityContextHolder还提供了其他工作模式。

比如有些应用,像Java Swing客户端应用,它就可能希望JVM中所有的线程使用同一个安全上下文。此时我们可以在启动阶段将SecurityContextHolder配置成全局策略MODE_GLOBAL。

还有其他的一些应用会有自己的线程创建,并且希望这些新建线程也能使用创建者的安全上下文。这种效果,可以通过将SecurityContextHolder配置成MODE_INHERITABLETHREADLOCAL策略达到。

获取当前用户信息

在SecurityContextHolder中保存的是当前访问者的信息。Spring Security使用一个Authentication对象来表示这个信息。一般情况下,我们都不需要创建这个对象,在登录过程中,Spring Security已经创建了该对象并帮我们放到了SecurityContextHolder中。从SecurityContextHolder中获取这个对象也是很简单的。比如,获取当前登录用户的用户名,可以这样 :

// 获取安全上下文对象,就是那个保存在 ThreadLocal 里面的安全上下文对象

// 总是不为null(如果不存在,则创建一个authentication属性为null的empty安全上下文对象)

SecurityContext securityContext = SecurityContextHolder.getContext();

// 获取当前认证了的 principal(当事人),或者 request token (令牌)

// 如果没有认证,会是 null,该例子是认证之后的情况

Authentication authentication = securityContext.getAuthentication()

// 获取当事人信息对象,返回结果是 Object 类型,但实际上可以是应用程序自定义的带有更多应用相关信息的某个类型。

// 很多情况下,该对象是 Spring Security 核心接口 UserDetails 的一个实现类,你可以把 UserDetails 想像

// 成我们数据库中保存的一个用户信息到 SecurityContextHolder 中 Spring Security 需要的用户信息格式的

// 一个适配器。

Object principal = authentication.getPrincipal();

if (principal instanceof UserDetails) {

String username = ((UserDetails)principal).getUsername();

} else {

String username = principal.toString();

}

修改SecurityContextHolder的工作模式

综上所述,SecurityContextHolder可以工作在以下三种模式之一:

MODE_THREADLOCAL (缺省工作模式)

MODE_GLOBAL

MODE_INHERITABLETHREADLOCAL

修改SecurityContextHolder的工作模式有两种方法 :

SecurityContextHolder会自动从该系统属性中尝试获取被设定的工作模式:设置一个系统属性(system.properties) : spring.security.strategy;

程序化方式主动设置工作模式的方法:调用SecurityContextHolder静态方法setStrategyName()

源码

/**

* 将一个给定的SecurityContext绑定到当前执行线程。

*/

public class SecurityContextHolder {

// ~ Static fields/initializers

// =====================================================================================

// 三种工作模式的定义,每种工作模式对应一种策略

public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";

public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";

public static final String MODE_GLOBAL = "MODE_GLOBAL";

// 类加载时首先尝试从环境属性中获取所指定的工作模式

public static final String SYSTEM_PROPERTY = "spring.security.strategy";

private static String strategyName = System.getProperty(SYSTEM_PROPERTY);

private static SecurityContextHolderStrategy strategy;

// 初始化计数器,初始为0,

// 1. 类加载过程中会被初始化一次,此值变为1

// 2. 此后每次调用 setStrategyName 会对新的策略对象执行一次初始化,相应的该值会增1

private static int initializeCount = 0;

static {

initialize();

}

// ~ Methods

// =====================================================================================

/**

* Explicitly clears the context value from the current thread.

*/

public static void clearContext() {

strategy.clearContext();

}

/**

* Obtain the current SecurityContext.

*

* @return the security context (never null)

*/

public static SecurityContext getContext() {

return strategy.getContext();

}

/**

* Primarily for troubleshooting purposes, this method shows how many times the class

* has re-initialized its SecurityContextHolderStrategy.

*

* @return the count (should be one unless you've called

* #setStrategyName(String) to switch to an alternate strategy.

*/

public static int getInitializeCount() {

return initializeCount;

}

private static void initialize() {

if (!StringUtils.hasText(strategyName)) {

// Set default, 设置缺省工作模式/策略 MODE_THREADLOCAL

strategyName = MODE_THREADLOCAL;

}

if (strategyName.equals(MODE_THREADLOCAL)) {

strategy = new ThreadLocalSecurityContextHolderStrategy();

}

else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {

strategy = new InheritableThreadLocalSecurityContextHolderStrategy();

}

else if (strategyName.equals(MODE_GLOBAL)) {

strategy = new GlobalSecurityContextHolderStrategy();

}

else {

// Try to load a custom strategy

try {

Class> clazz = Class.forName(strategyName);

Constructor> customStrategy = clazz.getConstructor();

strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();

}

catch (Exception ex) {

ReflectionUtils.handleReflectionException(ex);

}

}

initializeCount++;

}

/**

* Associates a new SecurityContext with the current thread of execution.

*

* @param context the new SecurityContext (may not be null)

*/

public static void setContext(SecurityContext context) {

strategy.setContext(context);

}

/**

* Changes the preferred strategy. Do NOT call this method more than once for

* a given JVM, as it will re-initialize the strategy and adversely affect any

* existing threads using the old strategy.

*

* @param strategyName the fully qualified class name of the strategy that should be

* used.

*/

public static void setStrategyName(String strategyName) {

SecurityContextHolder.strategyName = strategyName;

initialize();

}

/**

* Allows retrieval of the context strategy. See SEC-1188.

*

* @return the configured strategy for storing the security context.

*/

public static SecurityContextHolderStrategy getContextHolderStrategy() {

return strategy;

}

/**

* Delegates the creation of a new, empty context to the configured strategy.

*/

public static SecurityContext createEmptyContext() {

return strategy.createEmptyContext();

}

public String toString() {

return "SecurityContextHolder[strategy='" + strategyName + "'; initializeCount="

+ initializeCount + "]";

}

}

由源码可知,SecurityContextHolder利用了一个SecurityContextHolderStrategy(存储策略)进行上下文的存储。

SecurityContestHolderStrategy只是一个接口,这个接口提供创建、清空、获取、设置上下文的操作。

GlobalSecurityContextHolderStrategy: 全局的上下文存取策略,只存储一个上下文,对应前面说的单机系统。

ThreadLocalSecurityContextHolderStrategy: ThreadLocal内部会用数组来存储多个对象的。原理是,ThreadLocal会为每个线程开辟一个存储区域,来存储相应的对象。

Authentication——用户信息的表示:

在SecurityContextHolder中存储了当前与系统交互的用户的信息。Spring Security使用一个Authentication 对象来表示这些信息。一般不需要自己创建这个对象,但是查找这个对象的操作对用户来说却非常常见。

14012758.html

Principal(准则)=> 允许通过的规则,即允许访问的规则,基本等价于UserDetails(用户信息)

SecurityContext(安全上下文)只是保存了Authentication(认证信息)主要包含了以下内容

用户权限集合 => 可用于访问受保护资源时的权限验证

用户证书(密码) => 初次认证的时候,进行填充,认证成功后将被清空

细节 => 暂不清楚,猜测应该是记录哪些保护资源已经验证授权,下次不用再验证,等等。

Pirncipal => 大概就是账号吧

是否已认证成功

SpringSecurity底层认证流程

371c5849035dac75a48ea0fe5475861b.png

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java必知必会_Java必知必会-Spring Security 的相关文章

  • 使用Java实现七牛云OSS云存储上传图片至指定目录

    使用Java实现七牛云OSS云存储上传图片至指定目录 思路介绍 Controller代码 Util工具类代码 配置类 配置对象QnOssProperties 思路介绍 首先介绍下我的实现思路 前端通过Controller调用上传方法 上传方
  • 深分页优化总结

    前言 最近有面试过也遇到了问关于深分页问题 在这里简单从MySQL ES等方面分享一下自己对该问题认识和总结 一 深分页定义 可以从ES定义上来划分浅分页和深分页的边界 即页数超过10000页为深分页 少于10000页为浅分页 二 MySQ
  • QT打包发布全流程,超详细

    目录 第一步 配置环境变量 这一步不会的可以看我另一篇文章 QT 打包发布之环境变量配置 简单四步搞定 第三步 进行初步测试 主演是看你的程序是否有错 第四步 程序能运行 就可以在上层目录中看到生成了一个release文件夹 第五步 点开文
  • LeetCode周赛总结 第277场

    本文同步发布在我的个人博客 LeetCode周赛总结 第277场 欢迎访问 本次周赛没想到比上周还要简单 前三题都可以用非常简单的方法快速解决 第四题如果想对了方向其实也比较简单 元素计数 题目链接 元素计数 解题思路 相当基础的题目 要同
  • 华为OD机试 - 最佳植树距离(Java)

    题目描述 按照环保公司要求 小明需要在沙化严重的地区进行植树防沙工作 初步目标是种植一条直线的树带 由于有些区域目前不适合种植树木 所以只能在一些可以种植的点来种植树木 在树苗有限的情况下 要达到最佳效果 就要尽量散开种植 不同树苗之间的最
  • 【Java script基础学习】本地对象 - Date

    Date 日期对象 用来操作计算机的日期和时间 获取 获取当前日期时间 获取当前的时间戳 Date now 时间戳 从1970 1 1 0 0 0 到此刻的毫秒数 获取完整的日期对象 new Date 获取到的是一个对象类型的日期 包含日期
  • STM32MP157驱动开发——Linux LCD驱动(上)

    STM32MP157驱动开发 Linux LCD驱动 上 0 前言 一 LCD 和 LTDC 简介 1 LCD 简介 1 分辨率 2 像素格式 3 LCD 屏幕接口 4 LCD 时间参数 5 RGB LCD 屏幕时序 6 像素时钟 7 显存
  • 【Python pygame】零基础也能轻松掌握的学习路线与参考资料

    Python pygame是一款专门用于开发游戏和多媒体应用程序的Python库 它可以帮助开发者实现丰富多彩的图形界面和实时动态交互效果 本篇文章将为大家介绍Python pygame的学习路线 包括入门基础 进阶知识以及优秀实践 帮助大
  • C++中return语句的用法

    C 中的return语句是函数中一个重要的语句 return语句用于结束当前正在执行的函数 并将控制权返回给调用此函数的函数 return语句有两种形式 return return expression 1 没有返回值的函数 不带返回值的r
  • 贪吃蛇(一)--用C++编写一个简单的贪吃蛇

    这里简单介绍怎么用C 编写一个简单的黑白框的贪吃蛇游戏 复杂的加了可视化界面程序点击这里贪吃蛇 二 easyX图形库进行可视化界面制作 首先分析在黑白框中的贪吃蛇需要哪些功能 1 需要能在界面指定位置 x y 直接输出对应内容 2 需要动态
  • 学习的逻辑: 知识经济学

    来自http liguanglei name blogs 2012 11 28 the logic of learning 1 怎么证明学会了 2 你的身价是由你表现出来的知识决定的 不是你掌握的知识决定的 万物有始皆有终 我们的逻辑链条起
  • GEO分析

    title R Notebook output html notebook 1 下载加载包 cran packages lt c tidyr tibble dplyr stringr ggplot2 ggpubr factoextra Fa
  • 汽车租赁毕业设计

    一个基于Java汽车租赁管理系统的毕业设计网站 系统分为用户和管理员2个角色 详细介绍请见下文 一 环境信息 运行环境 java8 mysql5 6 开发语言 java 开发框架 springboot springmvc mybatis t
  • 物联网云平台系统设计

    物联网云平台系统设计 下面将谈到几个关键问题 设备如何接入网络 设备间如何通信 物联网数据的用途 如何搭建起一个物联网系统框架呢 它的技术架构又是怎么样呢 物联网终端软件系统架构 物联网云平台系统架构 1 物联网设备如何接入到网络 只有设备
  • VScode 中文显示出现黄色方框的解决方法

    VScode 中文显示出现黄色方框的解决方法 使用 VScode 打开源码时 发现注释中的汉字都被一个黄色的方框圈住了 这是因为使能了批注中字符的突出显示的功能 不喜欢这个黄色方框的小伙伴 可以参照下列步骤 禁用批注中字符的突出显示 将鼠标
  • 【统计学习方法】感知机Python 对偶形式实现

    代码可在Github上下载 https github com FlameCharmander MachineLearning 前言 上篇博文感知机的原始形式提到了感知机的原始形式 而这篇博文介绍的是感知机的对偶形式 算法理论 感知机的原始形
  • 数据集成:数据挖掘的准备工作之一

    欢迎来到我的博客 作者 秋无之地 简介 CSDN爬虫 后端 大数据领域创作者 目前从事python爬虫 后端和大数据等相关工作 主要擅长领域有 爬虫 后端 大数据开发 数据分析等 欢迎小伙伴们点赞 收藏 留言 关注 关注必回关 上一篇文章已
  • 人工智能算法分类

    一 人工智能学习算法分类 人工智能算法大体上来说可以分类两类 基于统计的机器学习算法 Machine Learning 和深度学习算法 Deep Learning 总的来说 在sklearn中机器学习算法大概的分类如下 纯算法类 1 回归算
  • Android Studio 自动配置Gradle失败处理方法,Could not install Gradle distribution from “....zip

    转载 https blog csdn net BraveAnt666 article details 124736258 提示的错误一般是https services gradle org distributions gradle 7 3

随机推荐

  • python后端学习(十三)路由支持正则、Url编码、增删改操作、增加log日志

    路由支持正则 编码 增删改操作 增加log日志 mini frame py import re url编码相关 import urllib parse import logging from pymysql import connect U
  • OpenMV输出PWM,实现对舵机控制

    OpenMV的定时器官方函数介绍 Timer类 控制内部定时器 目录 OpenMV的PWM资源介绍 为什么要用OpenMV输出PWM OpenMV的PWM资源分配 资源 注意 建议 PWM输出代码 代码讲解 Timer Timer chan
  • Matlab的fspecial函数

    参考 http www ilovematlab cn thread 52886 1 1 html 函数原型 h fspecial type h fspecial type para 根据函数原型对fspecial函数作个说明 fspecia
  • QT笔记-窗体创建方式

    1 非模式窗体 窗体创建方式1 Modal属性决定了show 应该将弹出的dialog设置为模式状态还是非模式状态 默认情况下改属性为false并且show 弹出的窗口是非模式状态的 把这个属性设置为true就相当于QWidget wind
  • webpack引入第三方库的方式,以及注意事项

    一般情况下 我们不用担心所使用的第三方库 在npm管理仓库中找不到 如果需要某一个库 如 jquery 可以直接运行npm install jquery脚本命令来安装这个项目所需要的依赖 然后 在使用jquery的模块文件中 通过impor
  • vs2019找不着工具箱了_VS2019 Nuget找不到包的问题处理

    VS不记得改了什么设置之后 发现找不到EF 解决办法 1 点击右侧的设置按钮 2 弹出窗中左侧树形结构选择 程序包源 再点击右上方的添加按钮 然后点击更新 确定按钮 再次搜索就可找到EF安装包 vs2019中NuGet控制台的常用命令 Db
  • 关于Flink Time中的Watermaker案例的详解

    需求 自定义数据源 产出交易订单数据 设置基于事件时间窗口统计 1 交易订单数据 import lombok AllArgsConstructor import lombok Data import lombok NoArgsConstru
  • 三个审稿人,两个同意,一个不同意,最后被录用希望大吗?

    链接 https www zhihu com question 340821507 编辑 深度学习与计算机视觉 声明 仅做学术分享 侵删 作者 知乎用户 https www zhihu com question 340821507 answ
  • 【转】C++类中对同类对象private成员访问

    私有成员变量的概念 在脑海中的现象是 以private关键字声明 是类的实现部分 不对外公开 不能在对象外部访问对象的私有成员变量 然而 在实现拷贝构造函数和赋值符函数时 在函数里利用对象直接访问了私有成员变量 因而 产生了困惑 下面以具体
  • 基于Matlab实现图像配准技术(附上源码+图像)

    图像配准是数字图像处理中的重要技术之一 它的目标是将多幅图像进行准确的对齐 使得它们在空间上保持一致 图像配准在许多领域都有广泛的应用 如医学影像 遥感图像 计算机视觉等 本文将介绍如何使用Matlab实现图像配准技术 并提供一个简单的案例
  • linux文件内容操作命令,linux 操作系统中cat查看文件内容命令的使用

    cat命令的用途是连接文件或标准输入并打印 这个命令常用来显示文件内容 或者将几个文件连接起来显示 或者从标准输入读取内容并显示 它常与重定向符号配合使用 1 命令格式 cat 选项 文件 2 命令功能 cat主要有三大功能 1 一次显示整
  • 华为安防产品VCN资料下载

    首先登录网站 华为入口 下载后安装 win10 然后就能看到客户端 注册登录 点击资料领航 进入网页 然后就可以下载了
  • 您的计算机已被Malloxx勒索病毒感染?恢复您的数据的方法在这里!

    导言 在数字时代 malloxx勒索病毒已经成为一个可怕的威胁 给个人 企业和组织带来了无法估量的损失 本文91数据恢复将深入探讨 malloxx勒索病毒的运作方式 并提供一些独特的方法来恢复被它加密的数据文件 以及如何在被其困扰之前采取
  • Linux高性能服务器编程|阅读笔记:第5章 - Linux网络编程基础API

    目录 简介 5 1 socket地址API 5 1 1 主机字节序和网络字节序 5 1 2 通用socket地址 5 1 3 专用socket地址 5 1 4 IP地址转换函数 5 2 创建socket 5 3 命名socket 5 4 监
  • 面试题目收集(1)

    本博客的 面试题目搜集系列不错 1 面试题目搜集1 2 面试题目搜集2 3 面试题目搜集3 4 面试题目搜集4 5 面试题目搜集5 6 面试题目搜集6 1 字符串取除多余的空格 包括行首的空格 单词与单词之间只能有多余的空格 结尾不能有空格
  • pytorch简单的逻辑回归

    import torch import torch nn as nn import torchvision import torchvision transforms as transforms Hyper parameters input
  • vue与ios、Android交互问题总结

    1 ios与vue交互可以显示iframe的页面内容 但是不能与iframe交互 Android可以 2 ios可以直接调用html页面 但是url传参只能是问号传参 不可用json对象 Android可以 3 Android对js语法要求
  • Spark高手之路3—Spark运行架构

    文章目录 Spark 运行架构 一 运行架构 二 核心组件 Driver Executor Master Worker ApplicationMaster 三 核心概念 1 Executor 与 Core 2 并行度 Parallelism
  • 【学习笔记向】零基础小白快速制作最简陋MMD(VRoid + Unity)

    特别鸣谢B站UP主十七时的大力支援 学生时期总会有一两个没有安排的周末 不如我们来嗑CP 学习 做MMD吧 第一天 下载软件和素材 熟悉Unity 下载软件可能需要很久 请耐心 一 软件 1 VRoid Studio 用于零基础建模 B站上
  • java必知必会_Java必知必会-Spring Security

    一 Spring Security介绍 1 框架介绍 Spring 是一个非常流行和成功的 Java 应用开发框架 Spring Security 基于 Spring 框架 提供了一套 Web 应用安全性的完整解决方案 一般来说 Web 应