Second, custom user name password authentication filter RestfulUsernamePasswordAuthenticationFilter
1、Registration filter mode
- Use /addFilterBefore/addFilterAfter to add filters to the filter chain, where addFilter can only add built-in filters, the order of which has been set in FilterOrderRegistration; addFilterBefore/addFilterAfter can add custom filters before/after the specified filters. addFilterAfter can add custom filters, add before/after the specified filter. The advantage of this approach is simple to use , the disadvantage is that you can not use spring security built-in components , and RestfulUsernamePasswordAuthenticationFilter need to use the AuthenticationManager component conflict , so do not use this approach .
- Use SecurityConfigurer to add filters to the filter chain by way of configuration classes, the official way to use. The advantage of this approach is that you can use spring security built-in components, but the disadvantage is that the implementation is more cumbersome, and you can only register filters that are set in the FilterOrderRegistration. This approach can use spring security built-in components, so to use this approach, you need to modify the FilterOrderRegistration to add custom filters.
2. Modify and override the filter order register
- The FilterOrderRegistration class is final and does not provide an open way to register custom filters, so you can only rewrite the class and add a custom filter order.
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import .x509.X509AuthenticationFilter;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
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(, ());
put(, ());
put(, ());
(); // gh-8105
put(, ());
put(, ());
put(, ());
put(, ());
put(, ());
put(, ());
put(, ());
(
"..OAuth2AuthorizationRequestRedirectFilter",
());
(
"..Saml2WebSsoAuthenticationRequestFilter",
());
put(, ());
put(, ());
("", ());
("..OAuth2LoginAuthenticationFilter",
());
(
"..Saml2WebSsoAuthenticationFilter",
());
//Adding custom filters
put(, ());
put(, ());
(); // gh-8105
("", ());
put(, ());
put(, ());
put(, ());
put(, ());
(
".",
());
put(, ());
put(, ());
put(, ());
put(, ());
put(, ());
put(, ());
("..OAuth2AuthorizationCodeGrantFilter",
());
put(, ());
put(, ());
put(, ());
put(, ());
put(, ());
}
/**
* 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 = ();
if ((className)) {
return;
}
(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 = (());
if (result != null) {
return result;
}
clazz = ();
}
return null;
}
private static class Step {
private final int stepSize;
private int value;
Step(int initialValue, int stepSize) {
= initialValue;
= stepSize;
}
int next() {
int value = ;
+= ;
return value;
}
}
}
3, create RestfulUsernamePasswordAuthenticationFilter
- References UsernamePasswordAuthenticationFilter
- Change the parameter fetch method from to from body body
- Create UsernamePasswordAuthenticationToken
- Setup Details
- Call the authenticate method of getAuthenticationManager() to get authentication information
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* Customized front-end and back-end separation/restfulUser name and password authentication filters for the method
* consultationUsernamePasswordAuthenticationFilter
*/
public class RestfulUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
//Does it only supportpostmethodologies
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 = ().equals(httpMethod);
= username;
= password;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException {
if ( && !().equals(())) {
throw new AuthenticationServiceException("Authentication method not supported: " + ());
} else {
Map<String, String> body = (request);
String name = (username);
String pswd = (password);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(name, pswd);
setDetails(request, authRequest);
return getAuthenticationManager().authenticate(authRequest);
}
}
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
((request));
}
}
4, create a custom user name password authentication filter configuration class RestfulLoginConfigurer
- References FormLoginConfigurer
- Register custom username password authentication filter RestfulUsernamePasswordAuthenticationFilter
- Setting the login address and request method
package ;
import ;
import ;
import ;
import ;
/**
* Customized front-end and back-end separation/restfulConfigurator for the username-password authentication filter for the mode,For registering authentication filters
* consultationFormLoginConfigurer
*/
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);
= loginMethod;
}
@Override
public RestfulLoginConfigurer<H> loginPage(String loginPage) {
return (loginPage);
}
@Override
public void init(H http) throws Exception {
(http);
}
@Override
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
return new AntPathRequestMatcher(loginProcessingUrl, loginMethod);
}
}
Third, custom security context repository SecurityContextRepositoryImpl
- Implementing a Secure Contextual Warehouse Based on Distributed Caching
- Get the context from the request header to get the token, get the context from the cache through the token, and return the null security context if it does not exist
- When saving the context get the token from the request header or logged in user information and save the token and context to the cache
1、Distributed cache interface and implementation
package ;
import ;
public interface CacheManager {
/**
* pass (a bill or inspection etc)tokenGetting Authentication Information
*
* @param token token
* @return Certification Information
*/
SecurityContext getSecurityContext(String token);
/**
* Does it containtoken
*
* @param token token
* @return Does it containtoken
*/
boolean contains(String token);
/**
* pass (a bill or inspection etc)token添加Certification Information
*
* @param token token
* @param securityContext Certification Information
*/
void addSecurityContext(String token, SecurityContext securityContext);
/**
* pass (a bill or inspection etc)token删除Certification Information
*
* @param token token
*/
void deleteSecurityContext(String token);
}
For the convenience of demonstration, here the expired Map, the actual use of the map will be changed to redis or other distributed cache can be
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
@Component
public class CacheManagerImpl implements CacheManager {
private static ExpiringMap<String, SecurityContext> SECURITY_CONTEXT_CACHE;
@PostConstruct
public void init() {
SECURITY_CONTEXT_CACHE = ().maxSize(200).expiration(30, ).expirationPolicy().variableExpiration().build();
}
@Override
public SecurityContext getSecurityContext(String token) {
return SECURITY_CONTEXT_CACHE.get(token);
}
@Override
public boolean contains(String token) {
return SECURITY_CONTEXT_CACHE.containsKey(token);
}
@Override
public void addSecurityContext(String token, SecurityContext securityContext) {
SECURITY_CONTEXT_CACHE.put(token, securityContext);
}
@Override
public void deleteSecurityContext(String token) {
SECURITY_CONTEXT_CACHE.remove(token);
}
}
2, create SecurityContextRepositoryImpl
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
@Component
public class SecurityContextRepositoryImpl implements SecurityContextRepository {
private static final String AUTHENTICATION = "Authentication";
@Autowired
private CacheManager cacheManager;
@Override
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
//Get thetoken,When not logged in to access the systemTokenempty
String token = ().getHeader(AUTHENTICATION);
if ((token)) {
SecurityContext securityContext = (token);
//securityContext已expire (as in expiration date)时empty
if ((securityContext)) {
return ();
}
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) ();
UserDetailsImpl userDetails = (UserDetailsImpl) ();
if ((())) {
//Falsified during testingToken(unmodifiedheadercap (a poem)body,modify onlysignaturepartial character)There is a probability of successful parsing,could besecretReasons for being too short,not pursued further,So here's the input under validationTokencap (a poem)缓存中的token
return securityContext;
}
}
return ();
}
@Override
public void saveContext(SecurityContext securityContext, HttpServletRequest request, HttpServletResponse response) {
//Get thetoken(Logout with,Not available at login)
String token = (AUTHENTICATION);
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) ();
if ((token) && (securityContext)) {
//not logged in、CAPTCHA, a type of challenge-response test (computing)、User name and password verification failed
return;
}
//At first loginTokenempty
if ((token)) {
UserDetailsImpl userDetails = (UserDetailsImpl) ();
//Login Successful
((), securityContext);
return;
}
//Withdrawal ortokenexpire (as in expiration date)(Setting in the cachetokenexpire (as in expiration date)时间)
if ((securityContext)) {
(token);
return;
}
//updateToken
(token, securityContext);
}
@Override
public boolean containsContext(HttpServletRequest request) {
//current versionSpring Securityonly ifSessionManagementFilterThe method is called in the
//disabledSessionManagementFilter,This method will not be called
String token = (AUTHENTICATION);
if ((token)) {
return false;
}
if ((token)) {
return false;
}
return (token);
}
}
Fourth, customize the user details UserDetailsImpl
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
@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) {
= username;
= password;
= enabled;
= accountNonExpired;
= credentialsNonExpired;
= accountNonLocked;
= grantedAuthorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
/**
* Whether the account has not expired
*
* @return true:be,false:clogged
*/
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
/**
* 账号beclogged未锁定
*
* @return true:be,false:clogged
*/
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
/**
* 密码beclogged未过期
*
* @return true:be,false:clogged
*/
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
/**
* 账号beclogged启用
*
* @return true:be,false:clogged
*/
@Override
public boolean isEnabled() {
return enabled;
}
}
V. Customized user details database query UserDetailsServiceImpl
package ;
import ;
import ;
import ;
import ;
import ;
import ;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
//@Autowired
//private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//TODO pass (a bill or inspection etc)usernameGetting users from the database,redirect usersUserDetails
//User user = (username);
//return new User(username, (), (), (), (), (), ());
//{noop}No password encryptor,cryptographic123All of them can be verified successfully
UserDetailsImpl userDetails = new UserDetailsImpl(username, "{noop}123", true, true, true, true, null);
//userDetailsset uptoken,ought totokenJust realizing the certification process,unusedjwt
(().toString());
return userDetails;
}
}
VI. Customized Logout Logout Results Processor
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
@Component
public class LoginResultHandler implements AuthenticationSuccessHandler, LogoutSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) authentication;
UserDetailsImpl userDetailsImpl = (UserDetailsImpl) ();
Map<String, Object> resp = new HashMap<>();
//00000show success
("code", "00000");
("token", ());
//generatingtokenBack to front end
(response, resp);
}
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Map<String, Object> resp = new HashMap<>();
//00000show success
("code", "00000");
(response, resp);
}
}
VII. Personalized configuration of the filter chain
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
//Login Parameters User Name
private static final String LOGIN_ARG_USERNAME = "username";
//Login Parameters Password
private static final String LOGIN_ARG_PASSWORD = "password";
//Login Request Type
private static final String LOGIN_HTTP_METHOD = ();
//Login request address
private static final String LOGIN_URL = "/login";
//Logout Request Address
private static final String LOGOUT_URL = "/logout";
@Autowired
private LoginResultHandler loginResultHandler;
@Autowired
private SecurityContextRepository securityContextRepository;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
//prohibit the use of sth.UsernamePasswordAuthenticationFilter、DefaultLoginPageGeneratingFilter、DefaultLogoutPageGeneratingFilter
.formLogin(FormLoginConfigurer::disable)
//prohibit the use of sth.BasicAuthenticationFilter
.httpBasic(HttpBasicConfigurer::disable)
//prohibit the use of sth.CsrfFilter
.csrf(CsrfConfigurer::disable)
//prohibit the use of sth.SessionManagementFilter
.sessionManagement(SessionManagementConfigurer::disable)
//httpRequest for accreditation
.authorizeHttpRequests(authorizeHttpRequestsCustomizer -> authorizeHttpRequestsCustomizer
//Any request
.anyRequest()
//Authentication required
.authenticated())
//Security Context Configuration
.securityContext(securityContextCustomizer -> securityContextCustomizer
//Setup CustomizationsecurityContextrepository
.securityContextRepository(securityContextRepository)
//Show SaveSecurityContext,Official Recommendations
.requireExplicitSave(true))
//Logout Configuration
.logout(logoutCustomizer -> logoutCustomizer
//logout address
.logoutUrl(LOGOUT_URL)
//Logout Success Processor
.logoutSuccessHandler(loginResultHandler)
)
//Register Configurator for Custom Login Filter:Automatic registration of customized login filters;
//Needs to be rewrittenFilterOrderRegistrationconstructorFilterOrderRegistration(){},Add the serial number of the custom filter in the constructor method,Otherwise the registration will not be successful
.apply(new RestfulLoginConfigurer<>(new RestfulUsernamePasswordAuthenticationFilter(LOGIN_ARG_USERNAME, LOGIN_ARG_PASSWORD, LOGIN_URL, LOGIN_HTTP_METHOD), LOGIN_URL, LOGIN_HTTP_METHOD))
//Setting the login address:The system generates a login page by default when it is not set.,login address/login
.loginPage(LOGIN_URL)
//Setting up the processor after a successful login
.successHandler(loginResultHandler);
//Creating a Filter Chain Object
return ();
}
}
VIII. Other categories
package ;
import ;
import ;
import ;
import ;
import ;
/**
* JSONtools
*
* @author admin
*/
public class JsonUtil {
private JsonUtil() {
throw new AssertionError();
}
/**
* object transferjson
*
* @param javaObject Objects or collections or arrays
* @return json
*/
public static String object2Json(Object javaObject) {
return (javaObject);
}
public static <K, V> Map<K, V> json2Map(String jsonString, Type type) {
return (jsonString, type);
}
}
package ;
import ;
import ;
/**
* SpringFramework Toolkit
*/
public class SecurityUtil {
private SecurityUtil() {
throw new AssertionError();
}
public static boolean isAuthenticated(SecurityContext securityContext) {
if (securityContext == null) {
return false;
}
Authentication authentication = ();
if (authentication == null) {
return false;
}
return ();
}
public static boolean isNotAuthenticated(SecurityContext securityContext) {
return !isAuthenticated(securityContext);
}
}
package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* SpringFramework Toolkit
*/
public class SpringUtil {
private SpringUtil() {
throw new AssertionError();
}
/**
* requestingbodyParameters are converted tomap
*
* @param request requesting
* @return parametersmap
* @throws IOException IOstream anomaly (geology)
*/
public static Map<String, String> rawBodyToMap(HttpServletRequest request) throws IOException {
BufferedReader streamReader = new BufferedReader(new InputStreamReader((), StandardCharsets.UTF_8));
StringBuilder responseStrBuilder = new StringBuilder();
String inputStr;
while ((inputStr = ()) != null) {
(inputStr);
}
return JsonUtil.json2Map((), );
}
public static void respJson(HttpServletResponse response, Map<String, Object> apiResp) throws IOException {
(MediaType.APPLICATION_JSON_VALUE);
(StandardCharsets.UTF_8.name());
().print(JsonUtil.object2Json(apiResp));
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="/POM/4.0.0" xmlns:xsi="http:///2001/XMLSchema-instance"
xsi:schemaLocation="/POM/4.0.0 /xsd/maven-4.0.">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId></groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId></groupId>
<artifactId>spring-boot-security2-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-security2-demo</name>
<description>Spring Bootintegrated (as in integrated circuit)Spring Securityexample</description>
<properties>
<>8</>
</properties>
<dependencies>
<dependency>
<groupId></groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--fastjson-->
<dependency>
<groupId></groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>
<!--expire (as in expiration date)map-->
<dependency>
<groupId></groupId>
<artifactId>expiringmap</artifactId>
<version>0.5.11</version>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
</dependency>
</dependencies>
</project>
IX. Access to case source code
- download address
- Private chats, comment sections, +V are all fine!