二、自定义用户名密码认证过滤器RestfulUsernamePasswordAuthenticationFilter
1、注册过滤器方式
- 使用httpSecurity.addFilter/addFilterBefore/addFilterAfter向过滤器链中添加过滤器,此中addFilter只能添加内置的过滤器,序次已在过滤器序次注册器(FilterOrderRegistration)中设置;addFilterBefore/addFilterAfter可以添加自定义过滤器,添加在指定的过滤器之前/之后。该方式优点是使用简单,缺点是无法使用spring security内置的组件,与RestfulUsernamePasswordAuthenticationFilter需要使用AuthenticationManager组件辩论,故不使用该方式。
- 使用SecurityConfigurer通过配置类的方式向过滤器链中添加过滤器,官方使用的方式。该方式优点是可以使用spring security内置的组件,缺点是实现较为笨重,而且只能注册过滤器序次注册器(FilterOrderRegistration)中设定的过滤器。该方式可以使用spring security内置的组件,所以采用本方式,需要修改过滤器序次注册器添加自定义的过滤器。
2、修改并覆盖过滤器序次注册器
- 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-8105
- put(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-8105
- this.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
- 参考UsernamePasswordAuthenticationFilter
- 将参数获取方式从request.getParameter改为从body体中
- 创建UsernamePasswordAuthenticationToken
- 设置细节
- 调用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;
- }
- @Override
- public 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
- 参考FormLoginConfigurer
- 注册自定义用户名密码认证过滤器RestfulUsernamePasswordAuthenticationFilter
- 设置登录地址和哀求方式
- 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;
- }
- @Override
- public RestfulLoginConfigurer<H> loginPage(String loginPage) {
- return super.loginPage(loginPage);
- }
- @Override
- public void init(H http) throws Exception {
- super.init(http);
- }
- @Override
- protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
- return new AntPathRequestMatcher(loginProcessingUrl, loginMethod);
- }
- }
复制代码 三、自定义安全上下文仓库SecurityContextRepositoryImpl
- 基于分布式缓存实现安全上下文仓库
- 获取上下文时从哀求头中获取token,通过token从缓存中获取上下文,不存在时返回空值安全上下文
- 保存上下文时从哀求头或者登任命户信息中获取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;
- @PostConstruct
- public void init() {
- SECURITY_CONTEXT_CACHE = ExpiringMap.builder().maxSize(200).expiration(30, TimeUnit.MINUTES).expirationPolicy(ExpirationPolicy.ACCESSED).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、创建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";
- @Autowired
- private CacheManager cacheManager;
- @Override
- public 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和缓存中的token
- return securityContext;
- }
- }
- return SecurityContextHolder.createEmptyContext();
- }
- @Override
- public 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;
- }
- //更新Token
- cacheManager.addSecurityContext(token, securityContext);
- }
- @Override
- public 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;
- }
- @Override
- public Collection<? extends GrantedAuthority> getAuthorities() {
- return authorities;
- }
- @Override
- public String getPassword() {
- return password;
- }
- @Override
- public String getUsername() {
- return username;
- }
- /**
- * 账号是否未过期
- *
- * @return true:是,false:否
- */
- @Override
- public boolean isAccountNonExpired() {
- return accountNonExpired;
- }
- /**
- * 账号是否未锁定
- *
- * @return true:是,false:否
- */
- @Override
- public boolean isAccountNonLocked() {
- return accountNonLocked;
- }
- /**
- * 密码是否未过期
- *
- * @return true:是,false:否
- */
- @Override
- public boolean isCredentialsNonExpired() {
- return credentialsNonExpired;
- }
- /**
- * 账号是否启用
- *
- * @return true:是,false:否
- */
- @Override
- public boolean isEnabled() {
- return enabled;
- }
- }
复制代码 九、案例源码获取
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |