七、Spring Boot集成Spring Security之前后分离认证最佳实现

一、Spring Boot集成Spring Security专栏

一、Spring Boot集成Spring Security之自动装配

二、Spring Boot集成Spring Security之实现原理

三、Spring Boot集成Spring Security之过滤器链详解

四、Spring Boot集成Spring Security之认证流程

五、Spring Boot集成Spring Security之认证流程2

六、Spring Boot集成Spring Security之前后分离认证流程最佳方案

七、Spring Boot集成Spring Security之前后分离认证最佳实现

二、自定义用户名密码认证过滤器RestfulUsernamePasswordAuthenticationFilter

1、注册过滤器方式

  1. 使用httpSecurity.addFilter/addFilterBefore/addFilterAfter向过滤器链中添加过滤器,其中addFilter只能添加内置的过滤器,顺序已在过滤器顺序注册器(FilterOrderRegistration)中设置;addFilterBefore/addFilterAfter可以添加自定义过滤器,添加在指定的过滤器之前/之后。该方式优点是使用简单,缺点是无法使用spring security内置的组件,与RestfulUsernamePasswordAuthenticationFilter需要使用AuthenticationManager组件冲突,故不使用该方式。
  2. 使用SecurityConfigurer通过配置类的方式向过滤器链中添加过滤器,官方使用的方式。该方式优点是可以使用spring security内置的组件,缺点是实现较为笨重,而且只能注册过滤器顺序注册器(FilterOrderRegistration)中设定的过滤器。该方式可以使用spring security内置的组件,所以采用本方式,需要修改过滤器顺序注册器添加自定义的过滤器。

2、修改并覆盖过滤器顺序注册器

  1. FilterOrderRegistration类为final类且未提供开放的注册自定义过滤器的方式,所以只能重写该类,并添加自定义过滤器的顺序
package org.springframework.security.config.annotation.web.builders;import com.yu.demo.spring.filter.RestfulUsernamePasswordAuthenticationFilter;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.DisableEncodeUrlFilter;
import org.springframework.security.web.session.ForceEagerSessionCreationFilter;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.web.filter.CorsFilter;import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;final class FilterOrderRegistration {private static final int INITIAL_ORDER = 100;private static final int ORDER_STEP = 100;private final Map<String, Integer> filterToOrder = new HashMap<>();FilterOrderRegistration() {Step order = new Step(INITIAL_ORDER, ORDER_STEP);put(DisableEncodeUrlFilter.class, order.next());put(ForceEagerSessionCreationFilter.class, order.next());put(ChannelProcessingFilter.class, order.next());order.next(); // gh-8105put(WebAsyncManagerIntegrationFilter.class, order.next());put(SecurityContextHolderFilter.class, order.next());put(SecurityContextPersistenceFilter.class, order.next());put(HeaderWriterFilter.class, order.next());put(CorsFilter.class, order.next());put(CsrfFilter.class, order.next());put(LogoutFilter.class, order.next());this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",order.next());this.filterToOrder.put("org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter",order.next());put(X509AuthenticationFilter.class, order.next());put(AbstractPreAuthenticatedProcessingFilter.class, order.next());this.filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter", order.next());this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",order.next());this.filterToOrder.put("org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter",order.next());//添加自定义过滤器put(RestfulUsernamePasswordAuthenticationFilter.class, order.next());put(UsernamePasswordAuthenticationFilter.class, order.next());order.next(); // gh-8105this.filterToOrder.put("org.springframework.security.openid.OpenIDAuthenticationFilter", order.next());put(DefaultLoginPageGeneratingFilter.class, order.next());put(DefaultLogoutPageGeneratingFilter.class, order.next());put(ConcurrentSessionFilter.class, order.next());put(DigestAuthenticationFilter.class, order.next());this.filterToOrder.put("org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter",order.next());put(BasicAuthenticationFilter.class, order.next());put(RequestCacheAwareFilter.class, order.next());put(SecurityContextHolderAwareRequestFilter.class, order.next());put(JaasApiIntegrationFilter.class, order.next());put(RememberMeAuthenticationFilter.class, order.next());put(AnonymousAuthenticationFilter.class, order.next());this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",order.next());put(SessionManagementFilter.class, order.next());put(ExceptionTranslationFilter.class, order.next());put(FilterSecurityInterceptor.class, order.next());put(AuthorizationFilter.class, order.next());put(SwitchUserFilter.class, order.next());}/*** Register a {@link Filter} with its specific position. If the {@link Filter} was* already registered before, the position previously defined is not going to be* overriden** @param filter   the {@link Filter} to register* @param position the position to associate with the {@link Filter}*/void put(Class<? extends Filter> filter, int position) {String className = filter.getName();if (this.filterToOrder.containsKey(className)) {return;}this.filterToOrder.put(className, position);}/*** Gets the order of a particular {@link Filter} class taking into consideration* superclasses.** @param clazz the {@link Filter} class to determine the sort order* @return the sort order or null if not defined*/Integer getOrder(Class<?> clazz) {while (clazz != null) {Integer result = this.filterToOrder.get(clazz.getName());if (result != null) {return result;}clazz = clazz.getSuperclass();}return null;}private static class Step {private final int stepSize;private int value;Step(int initialValue, int stepSize) {this.value = initialValue;this.stepSize = stepSize;}int next() {int value = this.value;this.value += this.stepSize;return value;}}}

3、创建RestfulUsernamePasswordAuthenticationFilter

  1. 参考UsernamePasswordAuthenticationFilter
  2. 将参数获取方式从request.getParameter改为从body体中
  3. 创建UsernamePasswordAuthenticationToken
  4. 设置细节
  5. 调用getAuthenticationManager()的authenticate方法获取认证信息
package com.yu.demo.spring.filter;import com.yu.demo.util.SpringUtil;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;/*** 自定义前后端分离/restful方式的用户名密码认证过滤器* 参考UsernamePasswordAuthenticationFilter*/
public class RestfulUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {//是否只支持post方法private final boolean postOnly;private final String username;private final String password;public RestfulUsernamePasswordAuthenticationFilter(String username, String password, String loginUrl, String httpMethod) {super(new AntPathRequestMatcher(loginUrl, httpMethod));postOnly = HttpMethod.POST.name().equals(httpMethod);this.username = username;this.password = password;}@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException {if (this.postOnly && !request.getMethod().equals(HttpMethod.POST.name())) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());} else {Map<String, String> body = SpringUtil.rawBodyToMap(request);String name = body.get(username);String pswd = body.get(password);UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(name, pswd);setDetails(request, authRequest);return getAuthenticationManager().authenticate(authRequest);}}protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));}}

4、创建自定义用户名密码认证过滤器配置类RestfulLoginConfigurer

  1. 参考FormLoginConfigurer
  2. 注册自定义用户名密码认证过滤器RestfulUsernamePasswordAuthenticationFilter
  3. 设置登录地址和请求方式
package com.yu.demo.spring.filter;import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;/*** 自定义前后端分离/restful方式的用户名密码验证过滤器配置器,用于注册认证过滤器* 参考FormLoginConfigurer*/
public class RestfulLoginConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractAuthenticationFilterConfigurer<H, RestfulLoginConfigurer<H>, RestfulUsernamePasswordAuthenticationFilter> {private final String loginMethod;public RestfulLoginConfigurer(RestfulUsernamePasswordAuthenticationFilter authenticationFilter, String defaultLoginProcessingUrl, String loginMethod) {super(authenticationFilter, defaultLoginProcessingUrl);this.loginMethod = loginMethod;}@Overridepublic RestfulLoginConfigurer<H> loginPage(String loginPage) {return super.loginPage(loginPage);}@Overridepublic void init(H http) throws Exception {super.init(http);}@Overrideprotected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {return new AntPathRequestMatcher(loginProcessingUrl, loginMethod);}
}

三、自定义安全上下文仓库SecurityContextRepositoryImpl

  1. 基于分布式缓存实现安全上下文仓库
  2. 获取上下文时从请求头中获取token,通过token从缓存中获取上下文,不存在时返回空值安全上下文
  3. 保存上下文时从请求头或者登录用户信息中获取token,将token和上下文保存到缓存中

1、分布式缓存接口和实现

package com.yu.demo.manager;import org.springframework.security.core.context.SecurityContext;public interface CacheManager {/*** 通过token获取认证信息** @param token token* @return 认证信息*/SecurityContext getSecurityContext(String token);/*** 是否包含token** @param token token* @return 是否包含token*/boolean contains(String token);/*** 通过token添加认证信息** @param token           token* @param securityContext 认证信息*/void addSecurityContext(String token, SecurityContext securityContext);/*** 通过token删除认证信息** @param token token*/void deleteSecurityContext(String token);}

为演示方便,这里采用过期Map,实际使用将map改为redis或者其他分布式缓存即可

package com.yu.demo.manager.impl;import com.yu.demo.manager.CacheManager;
import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;@Component
public class CacheManagerImpl implements CacheManager {private static ExpiringMap<String, SecurityContext> SECURITY_CONTEXT_CACHE;@PostConstructpublic void init() {SECURITY_CONTEXT_CACHE = ExpiringMap.builder().maxSize(200).expiration(30, TimeUnit.MINUTES).expirationPolicy(ExpirationPolicy.ACCESSED).variableExpiration().build();}@Overridepublic SecurityContext getSecurityContext(String token) {return SECURITY_CONTEXT_CACHE.get(token);}@Overridepublic boolean contains(String token) {return SECURITY_CONTEXT_CACHE.containsKey(token);}@Overridepublic void addSecurityContext(String token, SecurityContext securityContext) {SECURITY_CONTEXT_CACHE.put(token, securityContext);}@Overridepublic void deleteSecurityContext(String token) {SECURITY_CONTEXT_CACHE.remove(token);}
}

2、创建SecurityContextRepositoryImpl

package com.yu.demo.spring.impl;import com.yu.demo.entity.UserDetailsImpl;
import com.yu.demo.manager.CacheManager;
import com.yu.demo.util.SecurityUtil;
import org.apache.poi.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Component
public class SecurityContextRepositoryImpl implements SecurityContextRepository {private static final String AUTHENTICATION = "Authentication";@Autowiredprivate CacheManager cacheManager;@Overridepublic SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {//获取请求头中的token,未登录访问系统时Token为空String token = requestResponseHolder.getRequest().getHeader(AUTHENTICATION);if (StringUtil.isNotBlank(token)) {SecurityContext securityContext = cacheManager.getSecurityContext(token);//securityContext已过期时为空if (SecurityUtil.isNotAuthenticated(securityContext)) {return SecurityContextHolder.createEmptyContext();}UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) securityContext.getAuthentication();UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();if (token.equals(userDetails.getToken())) {//测试过程中伪造的Token(不修改header和body,只修改signature部分字符)有概率出现可以解析成功的情况,可能是secret太短的原因,未深究,所以这里在验证下输入的Token和缓存中的tokenreturn securityContext;}}return SecurityContextHolder.createEmptyContext();}@Overridepublic void saveContext(SecurityContext securityContext, HttpServletRequest request, HttpServletResponse response) {//获取请求头中的token(登出时有,登录时没有)String token = request.getHeader(AUTHENTICATION);UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) securityContext.getAuthentication();if (StringUtil.isBlank(token) && SecurityUtil.isNotAuthenticated(securityContext)) {//未登录、验证码、用户名密码校验失败return;}//第一次登录时Token为空if (StringUtil.isBlank(token)) {UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();//登录成功cacheManager.addSecurityContext(userDetails.getToken(), securityContext);return;}//退出或token过期(缓存中设置token过期时间)if (SecurityUtil.isNotAuthenticated(securityContext)) {cacheManager.deleteSecurityContext(token);return;}//更新TokencacheManager.addSecurityContext(token, securityContext);}@Overridepublic boolean containsContext(HttpServletRequest request) {//本版本的Spring Security只有SessionManagementFilter中调用该方法//已禁用SessionManagementFilter,该方法不会被调用String token = request.getHeader(AUTHENTICATION);if (StringUtil.isBlank(token)) {return false;}if (StringUtil.isBlank(token)) {return false;}return cacheManager.contains(token);}}

四、自定义用户详情UserDetailsImpl

package com.yu.demo.entity;import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;
import java.util.Set;@Setter
@Getter
@ToString
public class UserDetailsImpl implements UserDetails {private String password;private final String username;private final Set<GrantedAuthority> authorities;private final boolean accountNonExpired;private final boolean accountNonLocked;private final boolean credentialsNonExpired;private final boolean enabled;/*** token*/private String token;public UserDetailsImpl(String username, String password, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, boolean enabled, Set<GrantedAuthority> grantedAuthorities) {this.username = username;this.password = password;this.enabled = enabled;this.accountNonExpired = accountNonExpired;this.credentialsNonExpired = credentialsNonExpired;this.accountNonLocked = accountNonLocked;this.authorities = grantedAuthorities;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return authorities;}@Overridepublic String getPassword() {return password;}@Overridepublic String getUsername() {return username;}/*** 账号是否未过期** @return true:是,false:否*/@Overridepublic boolean isAccountNonExpired() {return accountNonExpired;}/*** 账号是否未锁定** @return true:是,false:否*/@Overridepublic boolean isAccountNonLocked() {return accountNonLocked;}/*** 密码是否未过期** @return true:是,false:否*/@Overridepublic boolean isCredentialsNonExpired() {return credentialsNonExpired;}/*** 账号是否启用** @return true:是,false:否*/@Overridepublic boolean isEnabled() {return enabled;}
}

五、自定义用户详情数据库查询UserDetailsServiceImpl

package com.yu.demo.spring.impl;import com.yu.demo.entity.UserDetailsImpl;
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.UUID;@Service
public class UserDetailsServiceImpl implements UserDetailsService {//@Autowired//private UserService userService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//TODO 通过username从数据库中获取用户,将用户转UserDetails//User user = userService.getByUsername(username);//return new User(username, user.getPassword(), user.getEnable(), user.getAccountNonExpired(), user.getCredentialsNonExpired(), user.getAccountNonLocked(), user.getAuthorities());//{noop}不使用密码加密器,密码123的都可以验证成功UserDetailsImpl userDetails = new UserDetailsImpl(username, "{noop}123", true, true, true, true, null);//userDetails中设置token,该token只是实现认证流程,未使用jwtuserDetails.setToken(UUID.randomUUID().toString());return userDetails;}}

六、自定义登出登出结果处理器

package com.yu.demo.spring.impl;import com.yu.demo.entity.UserDetailsImpl;
import com.yu.demo.util.SpringUtil;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;@Component
public class LoginResultHandler implements AuthenticationSuccessHandler, LogoutSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) authentication;UserDetailsImpl userDetailsImpl = (UserDetailsImpl) usernamePasswordAuthenticationToken.getPrincipal();Map<String, Object> resp = new HashMap<>();//00000表示成功resp.put("code", "00000");resp.put("token", userDetailsImpl.getToken());//生成token返回到前端SpringUtil.respJson(response, resp);}@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {Map<String, Object> resp = new HashMap<>();//00000表示成功resp.put("code", "00000");SpringUtil.respJson(response, resp);}
}

七、过滤器链个性化配置

package com.yu.demo.config;import com.yu.demo.spring.filter.RestfulLoginConfigurer;
import com.yu.demo.spring.filter.RestfulUsernamePasswordAuthenticationFilter;
import com.yu.demo.spring.impl.LoginResultHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.context.SecurityContextRepository;@Configuration
@EnableWebSecurity
public class SecurityConfig {//登录参数用户名private static final String LOGIN_ARG_USERNAME = "username";//登录参数密码private static final String LOGIN_ARG_PASSWORD = "password";//登录请求类型private static final String LOGIN_HTTP_METHOD = HttpMethod.POST.name();//登录请求地址private static final String LOGIN_URL = "/login";//登出请求地址private static final String LOGOUT_URL = "/logout";@Autowiredprivate LoginResultHandler loginResultHandler;@Autowiredprivate SecurityContextRepository securityContextRepository;@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {httpSecurity//禁用UsernamePasswordAuthenticationFilter、DefaultLoginPageGeneratingFilter、DefaultLogoutPageGeneratingFilter.formLogin(FormLoginConfigurer::disable)//禁用BasicAuthenticationFilter.httpBasic(HttpBasicConfigurer::disable)//禁用CsrfFilter.csrf(CsrfConfigurer::disable)//禁用SessionManagementFilter.sessionManagement(SessionManagementConfigurer::disable)//http请求认证.authorizeHttpRequests(authorizeHttpRequestsCustomizer -> authorizeHttpRequestsCustomizer//任何请求.anyRequest()//需要认证.authenticated())//安全上下文配置.securityContext(securityContextCustomizer -> securityContextCustomizer//设置自定义securityContext仓库.securityContextRepository(securityContextRepository)//显示保存SecurityContext,官方推荐.requireExplicitSave(true))//登出配置.logout(logoutCustomizer -> logoutCustomizer//登出地址.logoutUrl(LOGOUT_URL)//登出成功处理器.logoutSuccessHandler(loginResultHandler))//注册自定义登录过滤器的配置器:自动注册自定义登录过滤器;//需要重写FilterOrderRegistration的构造方法FilterOrderRegistration(){},在构造方法中添加自定义过滤器的序号,否则注册不成功.apply(new RestfulLoginConfigurer<>(new RestfulUsernamePasswordAuthenticationFilter(LOGIN_ARG_USERNAME, LOGIN_ARG_PASSWORD, LOGIN_URL, LOGIN_HTTP_METHOD), LOGIN_URL, LOGIN_HTTP_METHOD))//设置登录地址:未设置时系统默认生成登录页面,登录地址/login.loginPage(LOGIN_URL)//设置登录成功之后的处理器.successHandler(loginResultHandler);//创建过滤器链对象return httpSecurity.build();}}

八、其他类

package com.yu.demo.util;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;import java.lang.reflect.Type;
import java.util.Map;/*** JSON工具类** @author admin*/
public class JsonUtil {private JsonUtil() {throw new AssertionError();}/*** 对象转json** @param javaObject 对象或集合或者数组* @return json*/public static String object2Json(Object javaObject) {return JSONObject.toJSONString(javaObject);}public static <K, V> Map<K, V> json2Map(String jsonString, Type type) {return JSON.parseObject(jsonString, type);}
}
package com.yu.demo.util;import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;/*** Spring框架工具类*/
public class SecurityUtil {private SecurityUtil() {throw new AssertionError();}public static boolean isAuthenticated(SecurityContext securityContext) {if (securityContext == null) {return false;}Authentication authentication = securityContext.getAuthentication();if (authentication == null) {return false;}return authentication.isAuthenticated();}public static boolean isNotAuthenticated(SecurityContext securityContext) {return !isAuthenticated(securityContext);}}
package com.yu.demo.util;import org.springframework.http.MediaType;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Map;/*** Spring框架工具类*/
public class SpringUtil {private SpringUtil() {throw new AssertionError();}/*** 请求body参数转为map** @param request 请求* @return 参数map* @throws IOException IO流异常*/public static Map<String, String> rawBodyToMap(HttpServletRequest request) throws IOException {BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8));StringBuilder responseStrBuilder = new StringBuilder();String inputStr;while ((inputStr = streamReader.readLine()) != null) {responseStrBuilder.append(inputStr);}return JsonUtil.json2Map(responseStrBuilder.toString(), Map.class);}public static void respJson(HttpServletResponse response, Map<String, Object> apiResp) throws IOException {response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding(StandardCharsets.UTF_8.name());response.getWriter().print(JsonUtil.object2Json(apiResp));}}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.18</version><relativePath/></parent><groupId>com.yu</groupId><artifactId>spring-boot-security2-demo</artifactId><version>0.0.1-SNAPSHOT</version><name>spring-boot-security2-demo</name><description>Spring Boot集成Spring Security样例</description><properties><java.version>8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!--fastjson--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.46</version></dependency><!--过期map--><dependency><groupId>net.jodah</groupId><artifactId>expiringmap</artifactId><version>0.5.11</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.0</version></dependency></dependencies></project>

九、案例源码获取

  • CSDN下载地址
  • 私聊、评论区、+V均可

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

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

相关文章

数字马力二面面试总结

24.03.07数字马力二面面试总结 前段时间找工作,做的一些面试笔记总结 大家有面试录音或者记录的也可以发给我,我来整理答案呀 数字马力二面面试总结 24.03.07数字马力二面面试总结你可以挑一个你的最有挑战性的,有难度的,最具有复杂性的项目,可以简单说一下。有没有和算…

C语言例题练手(1)

前几篇博客的内容已经涉及了C语言的部分语法知识&#xff0c;我们可以尝试做一些编程题&#xff0c;或者换一种说法就是可以写出什么样的程序以此来解决一些问题。 题目来自牛客网https://www.nowcoder.com和C语言菜鸟教程C 语言教程 | 菜鸟教程 数值计算 【例1】带余除法计…

大模型LLama3!!!Ollama下载、部署和应用(保姆级详细教程)

首先呢&#xff0c;大家在网站先下载ollama软件 这就和anaconda和python是一样的 废话不多说 直接上链接&#xff1a;Download Ollama on Windows 三个系统都支持 注意&#xff1a; 这里的Models&#xff0c;就是在上面&#xff0c;大家点开之后&#xff0c;里面有很多模型…

【359】基于springboot的智慧草莓基地管理系统

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本智慧草莓基地管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据…

MongoDB笔记03-MongoDB索引

文章目录 一、前言1.1 概述1.2 MongoDB索引使用B-Tree还是BTree&#xff1f;1.3 B 树和 B 树的对比1.4 总结 二、索引的类型2.1 单字段索引2.2 复合索引2.3 其他索引 三、索引的管理操作3.1 索引的查看3.2 索引的创建3.2.1 单字段索引3.2.2 复合索引 3.3 索引的移除3.3.1 指定索…

string模拟实现流插入(输出)+流提取(输入)

个人主页&#xff1a;Jason_from_China-CSDN博客 所属栏目&#xff1a;C系统性学习_Jason_from_China的博客-CSDN博客 所属栏目&#xff1a;C知识点的补充_Jason_from_China的博客-CSDN博客 string模拟实现clear 模拟实现clear的目的是在流提取的时候我们清空之前的数据&#x…

C++入门基础知识134—【关于C 库函数 - gmtime()】

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于C 库函数 - gmtime()的相关内容&#xf…

ERP学习笔记-预处理eeglab

第一步&#xff1a;数据格式转化 import data&#xff1a;读取收集到的原始数据文件.vhdr格式 读取后的样子&#xff1a; 将数据保存为.set文件 第二步&#xff1a;通道定位 读取.set文件 Channel locations部分为unknown&#xff0c;表明通道的坐标未知 增加默认的设置 Chan…

查缺补漏----用户上网过程(HTTP,DNS与ARP)

&#xff08;1&#xff09;HTTP 来自湖科大计算机网络微课堂&#xff1a; ① HTTP/1.0采用非持续连接方式。在该方式下&#xff0c;每次浏览器要请求一个文件都要与服务器建立TCP连接当收到响应后就立即关闭连接。 每请求一个文档就要有两倍的RTT的开销。若一个网页上有很多引…

谷歌推出全新AI生成游戏玩法 —— 无限生成角色生活模拟游戏“Unbounded”

随着人工智能技术的飞速发展,游戏行业正迎来前所未有的创新。近日,谷歌宣布了一款名为“Unbounded”的新型游戏,这是一款基于生成式AI技术的角色生命模拟游戏,它将为玩家带来前所未有的开放性和互动性体验。 项目概览 项目名称:Unbounded类型:生成式无限游戏(Generati…

论文阅读:DynamicDet: A Unified Dynamic Architecture for Object Detection

论文地址&#xff1a;[2304.05552] DynamicDet: A Unified Dynamic Architecture for Object Detection 代码地址&#xff1a;GitHub - VDIGPKU/DynamicDet: [CVPR 2023] DynamicDet: A Unified Dynamic Architecture for Object Detection 概要 本文提出了一种名为 DynamicD…

关于在GitLab的CI/CD中用docker buildx本地化多架构打包dotnet应用的问题

关于在GitLab的CI/CD中用docker buildx本地化多架构打包dotnet应用的问题 这是一个DevOps综合性问题docker buildx多架构打包.NET应用的问题用QEMU模拟多架构环境打包 这是一个DevOps综合性问题 网络上的方案都是细分的领域&#xff0c;未见一个集成了GitLabdockerdotnet的多架…

翻译工具开发技术笔记:《老挝语翻译通》app支持语音识别翻译功能,怎么提高语音识别的准确度呢?

《老挝语翻译通》app是一款专为老挝语翻译设计的免费工具&#xff0c;支持文本翻译、老挝文OCR文字识别提取、文字转语音。这款工具以其技术优势和用户友好的界面&#xff0c;为用户提供了便捷的老挝语翻译体验。 技术特点 文本翻译&#xff1a;支持双语输入&#xff0c;提供精…

qt QListView详解

1、概述 QListView 是 Qt 框架中的一个视图类&#xff0c;用于展示模型中的数据。它基于 QAbstractItemView&#xff0c;支持多种视图模式&#xff0c;如列表视图&#xff08;List View&#xff09;、图标视图&#xff08;Icon View&#xff09;等。QListView 是模型/视图框架…

初识C++(上) -- C++的关键字、命名空间、缺省参数以及函数的重载

目录 一、C的关键字&#xff08;C98&#xff09; 二、命名空间 1、命名冲突 2、命名空间 2.1 命名空间的定义 (1). 命名空间定义的例子以及命名空间的嵌套&#xff1a; (2). 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中&#xff1a; 2…

MySQL_客户端工具建库.

前言&#xff1a; 通过前面的学习我们已经了解到什么是数据库&#xff0c;以及数据库是如何安装的&#xff0c;相信大家都已将数据库安装好了&#xff0c;让我们接下来开始新的学习吧&#xff01;&#xff01;&#xff01; 1.MySQL客户端工具 1. MySQL Workbench MySQL :: D…

突破1200°C高温性能极限!北京科技大学用机器学习合成24种耐火高熵合金,室温延展性极佳

在工程应用中&#xff0c;如燃气轮机、核反应堆和航空推进系统&#xff0c;对具备优异高温机械性能的金属合金需求十分旺盛。由于材料熔点的固有限制&#xff0c;传统镍基 (Ni) 高温合金的耐温能力已接近极限。为满足开发高温结构材料的需求&#xff0c;耐火高熵合金 (RHEAs) 于…

leetcode21:合并两个有序列表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#xff1a;[]示…

开源模型应用落地-glm模型小试-glm-4-9b-chat-vLLM集成(四)

一、前言 GLM-4是智谱AI团队于2024年1月16日发布的基座大模型&#xff0c;旨在自动理解和规划用户的复杂指令&#xff0c;并能调用网页浏览器。其功能包括数据分析、图表创建、PPT生成等&#xff0c;支持128K的上下文窗口&#xff0c;使其在长文本处理和精度召回方面表现优异&a…

K8S篇(基本介绍)

目录 一、什么是Kubernetes&#xff1f; 二、Kubernetes管理员认证&#xff08;CKA&#xff09; 1. 简介 2. 考试难易程度 3. 考试时长 4. 多少分及格 5. 考试费用 三、Kubernetes整体架构 Master Nodes 四、Kubernetes架构及和核心组件 五、Kubernetes各个组件及功…