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

打印 上一主题 下一主题

主题 878|帖子 878|积分 2634

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

1、注册过滤器方式


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


  • FilterOrderRegistration类为final类且未提供开放的注册自定义过滤器的方式,所以只能重写该类,并添加自定义过滤器的序次
  1. package org.springframework.security.config.annotation.web.builders;
  2. import com.yu.demo.spring.filter.RestfulUsernamePasswordAuthenticationFilter;
  3. import org.springframework.security.web.access.ExceptionTranslationFilter;
  4. import org.springframework.security.web.access.channel.ChannelProcessingFilter;
  5. import org.springframework.security.web.access.intercept.AuthorizationFilter;
  6. import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
  7. import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
  8. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  9. import org.springframework.security.web.authentication.logout.LogoutFilter;
  10. import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
  11. import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
  12. import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
  13. import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
  14. import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
  15. import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter;
  16. import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
  17. import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
  18. import org.springframework.security.web.context.SecurityContextHolderFilter;
  19. import org.springframework.security.web.context.SecurityContextPersistenceFilter;
  20. import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
  21. import org.springframework.security.web.csrf.CsrfFilter;
  22. import org.springframework.security.web.header.HeaderWriterFilter;
  23. import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
  24. import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
  25. import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
  26. import org.springframework.security.web.session.ConcurrentSessionFilter;
  27. import org.springframework.security.web.session.DisableEncodeUrlFilter;
  28. import org.springframework.security.web.session.ForceEagerSessionCreationFilter;
  29. import org.springframework.security.web.session.SessionManagementFilter;
  30. import org.springframework.web.filter.CorsFilter;
  31. import javax.servlet.Filter;
  32. import java.util.HashMap;
  33. import java.util.Map;
  34. final class FilterOrderRegistration {
  35.     private static final int INITIAL_ORDER = 100;
  36.     private static final int ORDER_STEP = 100;
  37.     private final Map<String, Integer> filterToOrder = new HashMap<>();
  38.     FilterOrderRegistration() {
  39.         Step order = new Step(INITIAL_ORDER, ORDER_STEP);
  40.         put(DisableEncodeUrlFilter.class, order.next());
  41.         put(ForceEagerSessionCreationFilter.class, order.next());
  42.         put(ChannelProcessingFilter.class, order.next());
  43.         order.next(); // gh-8105
  44.         put(WebAsyncManagerIntegrationFilter.class, order.next());
  45.         put(SecurityContextHolderFilter.class, order.next());
  46.         put(SecurityContextPersistenceFilter.class, order.next());
  47.         put(HeaderWriterFilter.class, order.next());
  48.         put(CorsFilter.class, order.next());
  49.         put(CsrfFilter.class, order.next());
  50.         put(LogoutFilter.class, order.next());
  51.         this.filterToOrder.put(
  52.                 "org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",
  53.                 order.next());
  54.         this.filterToOrder.put(
  55.                 "org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter",
  56.                 order.next());
  57.         put(X509AuthenticationFilter.class, order.next());
  58.         put(AbstractPreAuthenticatedProcessingFilter.class, order.next());
  59.         this.filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter", order.next());
  60.         this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",
  61.                 order.next());
  62.         this.filterToOrder.put(
  63.                 "org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter",
  64.                 order.next());
  65.         //添加自定义过滤器
  66.         put(RestfulUsernamePasswordAuthenticationFilter.class, order.next());
  67.         put(UsernamePasswordAuthenticationFilter.class, order.next());
  68.         order.next(); // gh-8105
  69.         this.filterToOrder.put("org.springframework.security.openid.OpenIDAuthenticationFilter", order.next());
  70.         put(DefaultLoginPageGeneratingFilter.class, order.next());
  71.         put(DefaultLogoutPageGeneratingFilter.class, order.next());
  72.         put(ConcurrentSessionFilter.class, order.next());
  73.         put(DigestAuthenticationFilter.class, order.next());
  74.         this.filterToOrder.put(
  75.                 "org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter",
  76.                 order.next());
  77.         put(BasicAuthenticationFilter.class, order.next());
  78.         put(RequestCacheAwareFilter.class, order.next());
  79.         put(SecurityContextHolderAwareRequestFilter.class, order.next());
  80.         put(JaasApiIntegrationFilter.class, order.next());
  81.         put(RememberMeAuthenticationFilter.class, order.next());
  82.         put(AnonymousAuthenticationFilter.class, order.next());
  83.         this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",
  84.                 order.next());
  85.         put(SessionManagementFilter.class, order.next());
  86.         put(ExceptionTranslationFilter.class, order.next());
  87.         put(FilterSecurityInterceptor.class, order.next());
  88.         put(AuthorizationFilter.class, order.next());
  89.         put(SwitchUserFilter.class, order.next());
  90.     }
  91.     /**
  92.      * Register a {@link Filter} with its specific position. If the {@link Filter} was
  93.      * already registered before, the position previously defined is not going to be
  94.      * overriden
  95.      *
  96.      * @param filter   the {@link Filter} to register
  97.      * @param position the position to associate with the {@link Filter}
  98.      */
  99.     void put(Class<? extends Filter> filter, int position) {
  100.         String className = filter.getName();
  101.         if (this.filterToOrder.containsKey(className)) {
  102.             return;
  103.         }
  104.         this.filterToOrder.put(className, position);
  105.     }
  106.     /**
  107.      * Gets the order of a particular {@link Filter} class taking into consideration
  108.      * superclasses.
  109.      *
  110.      * @param clazz the {@link Filter} class to determine the sort order
  111.      * @return the sort order or null if not defined
  112.      */
  113.     Integer getOrder(Class<?> clazz) {
  114.         while (clazz != null) {
  115.             Integer result = this.filterToOrder.get(clazz.getName());
  116.             if (result != null) {
  117.                 return result;
  118.             }
  119.             clazz = clazz.getSuperclass();
  120.         }
  121.         return null;
  122.     }
  123.     private static class Step {
  124.         private final int stepSize;
  125.         private int value;
  126.         Step(int initialValue, int stepSize) {
  127.             this.value = initialValue;
  128.             this.stepSize = stepSize;
  129.         }
  130.         int next() {
  131.             int value = this.value;
  132.             this.value += this.stepSize;
  133.             return value;
  134.         }
  135.     }
  136. }
复制代码
3、创建RestfulUsernamePasswordAuthenticationFilter


  • 参考UsernamePasswordAuthenticationFilter
  • 将参数获取方式从request.getParameter改为从body体中
  • 创建UsernamePasswordAuthenticationToken
  • 设置细节
  • 调用getAuthenticationManager()的authenticate方法获取认证信息
  1. package com.yu.demo.spring.filter;
  2. import com.yu.demo.util.SpringUtil;
  3. import org.springframework.http.HttpMethod;
  4. import org.springframework.security.authentication.AuthenticationServiceException;
  5. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  6. import org.springframework.security.core.Authentication;
  7. import org.springframework.security.core.AuthenticationException;
  8. import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
  9. import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletResponse;
  12. import java.io.IOException;
  13. import java.util.Map;
  14. /**
  15. * 自定义前后端分离/restful方式的用户名密码认证过滤器
  16. * 参考UsernamePasswordAuthenticationFilter
  17. */
  18. public class RestfulUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
  19.     //是否只支持post方法
  20.     private final boolean postOnly;
  21.     private final String username;
  22.     private final String password;
  23.     public RestfulUsernamePasswordAuthenticationFilter(String username, String password, String loginUrl, String httpMethod) {
  24.         super(new AntPathRequestMatcher(loginUrl, httpMethod));
  25.         postOnly = HttpMethod.POST.name().equals(httpMethod);
  26.         this.username = username;
  27.         this.password = password;
  28.     }
  29.     @Override
  30.     public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException {
  31.         if (this.postOnly && !request.getMethod().equals(HttpMethod.POST.name())) {
  32.             throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
  33.         } else {
  34.             Map<String, String> body = SpringUtil.rawBodyToMap(request);
  35.             String name = body.get(username);
  36.             String pswd = body.get(password);
  37.             UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(name, pswd);
  38.             setDetails(request, authRequest);
  39.             return getAuthenticationManager().authenticate(authRequest);
  40.         }
  41.     }
  42.     protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
  43.         authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
  44.     }
  45. }
复制代码
4、创建自定义用户名密码认证过滤器配置类RestfulLoginConfigurer


  • 参考FormLoginConfigurer
  • 注册自定义用户名密码认证过滤器RestfulUsernamePasswordAuthenticationFilter
  • 设置登录地址和哀求方式
  1. package com.yu.demo.spring.filter;
  2. import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
  3. import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
  4. import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
  5. import org.springframework.security.web.util.matcher.RequestMatcher;
  6. /**
  7. * 自定义前后端分离/restful方式的用户名密码验证过滤器配置器,用于注册认证过滤器
  8. * 参考FormLoginConfigurer
  9. */
  10. public class RestfulLoginConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractAuthenticationFilterConfigurer<H, RestfulLoginConfigurer<H>, RestfulUsernamePasswordAuthenticationFilter> {
  11.     private final String loginMethod;
  12.     public RestfulLoginConfigurer(RestfulUsernamePasswordAuthenticationFilter authenticationFilter, String defaultLoginProcessingUrl, String loginMethod) {
  13.         super(authenticationFilter, defaultLoginProcessingUrl);
  14.         this.loginMethod = loginMethod;
  15.     }
  16.     @Override
  17.     public RestfulLoginConfigurer<H> loginPage(String loginPage) {
  18.         return super.loginPage(loginPage);
  19.     }
  20.     @Override
  21.     public void init(H http) throws Exception {
  22.         super.init(http);
  23.     }
  24.     @Override
  25.     protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
  26.         return new AntPathRequestMatcher(loginProcessingUrl, loginMethod);
  27.     }
  28. }
复制代码
三、自定义安全上下文仓库SecurityContextRepositoryImpl


  • 基于分布式缓存实现安全上下文仓库
  • 获取上下文时从哀求头中获取token,通过token从缓存中获取上下文,不存在时返回空值安全上下文
  • 保存上下文时从哀求头或者登任命户信息中获取token,将token和上下文保存到缓存中
1、分布式缓存接口和实现
  1. package com.yu.demo.manager;
  2. import org.springframework.security.core.context.SecurityContext;
  3. public interface CacheManager {
  4.     /**
  5.      * 通过token获取认证信息
  6.      *
  7.      * @param token token
  8.      * @return 认证信息
  9.      */
  10.     SecurityContext getSecurityContext(String token);
  11.     /**
  12.      * 是否包含token
  13.      *
  14.      * @param token token
  15.      * @return 是否包含token
  16.      */
  17.     boolean contains(String token);
  18.     /**
  19.      * 通过token添加认证信息
  20.      *
  21.      * @param token           token
  22.      * @param securityContext 认证信息
  23.      */
  24.     void addSecurityContext(String token, SecurityContext securityContext);
  25.     /**
  26.      * 通过token删除认证信息
  27.      *
  28.      * @param token token
  29.      */
  30.     void deleteSecurityContext(String token);
  31. }
复制代码
为演示方便,这里采用过期Map,实际使用将map改为redis或者其他分布式缓存即可
  1. package com.yu.demo.manager.impl;
  2. import com.yu.demo.manager.CacheManager;
  3. import net.jodah.expiringmap.ExpirationPolicy;
  4. import net.jodah.expiringmap.ExpiringMap;
  5. import org.springframework.security.core.context.SecurityContext;
  6. import org.springframework.stereotype.Component;
  7. import javax.annotation.PostConstruct;
  8. import java.util.concurrent.TimeUnit;
  9. @Component
  10. public class CacheManagerImpl implements CacheManager {
  11.     private static ExpiringMap<String, SecurityContext> SECURITY_CONTEXT_CACHE;
  12.     @PostConstruct
  13.     public void init() {
  14.         SECURITY_CONTEXT_CACHE = ExpiringMap.builder().maxSize(200).expiration(30, TimeUnit.MINUTES).expirationPolicy(ExpirationPolicy.ACCESSED).variableExpiration().build();
  15.     }
  16.     @Override
  17.     public SecurityContext getSecurityContext(String token) {
  18.         return SECURITY_CONTEXT_CACHE.get(token);
  19.     }
  20.     @Override
  21.     public boolean contains(String token) {
  22.         return SECURITY_CONTEXT_CACHE.containsKey(token);
  23.     }
  24.     @Override
  25.     public void addSecurityContext(String token, SecurityContext securityContext) {
  26.         SECURITY_CONTEXT_CACHE.put(token, securityContext);
  27.     }
  28.     @Override
  29.     public void deleteSecurityContext(String token) {
  30.         SECURITY_CONTEXT_CACHE.remove(token);
  31.     }
  32. }
复制代码
2、创建SecurityContextRepositoryImpl
  1. package com.yu.demo.spring.impl;
  2. import com.yu.demo.entity.UserDetailsImpl;
  3. import com.yu.demo.manager.CacheManager;
  4. import com.yu.demo.util.SecurityUtil;
  5. import org.apache.poi.util.StringUtil;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  8. import org.springframework.security.core.context.SecurityContext;
  9. import org.springframework.security.core.context.SecurityContextHolder;
  10. import org.springframework.security.web.context.HttpRequestResponseHolder;
  11. import org.springframework.security.web.context.SecurityContextRepository;
  12. import org.springframework.stereotype.Component;
  13. import javax.servlet.http.HttpServletRequest;
  14. import javax.servlet.http.HttpServletResponse;
  15. @Component
  16. public class SecurityContextRepositoryImpl implements SecurityContextRepository {
  17.     private static final String AUTHENTICATION = "Authentication";
  18.     @Autowired
  19.     private CacheManager cacheManager;
  20.     @Override
  21.     public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
  22.         //获取请求头中的token,未登录访问系统时Token为空
  23.         String token = requestResponseHolder.getRequest().getHeader(AUTHENTICATION);
  24.         if (StringUtil.isNotBlank(token)) {
  25.             SecurityContext securityContext = cacheManager.getSecurityContext(token);
  26.             //securityContext已过期时为空
  27.             if (SecurityUtil.isNotAuthenticated(securityContext)) {
  28.                 return SecurityContextHolder.createEmptyContext();
  29.             }
  30.             UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) securityContext.getAuthentication();
  31.             UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
  32.             if (token.equals(userDetails.getToken())) {
  33.                 //测试过程中伪造的Token(不修改header和body,只修改signature部分字符)有概率出现可以解析成功的情况,可能是secret太短的原因,未深究,所以这里在验证下输入的Token和缓存中的token
  34.                 return securityContext;
  35.             }
  36.         }
  37.         return SecurityContextHolder.createEmptyContext();
  38.     }
  39.     @Override
  40.     public void saveContext(SecurityContext securityContext, HttpServletRequest request, HttpServletResponse response) {
  41.         //获取请求头中的token(登出时有,登录时没有)
  42.         String token = request.getHeader(AUTHENTICATION);
  43.         UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) securityContext.getAuthentication();
  44.         if (StringUtil.isBlank(token) && SecurityUtil.isNotAuthenticated(securityContext)) {
  45.             //未登录、验证码、用户名密码校验失败
  46.             return;
  47.         }
  48.         //第一次登录时Token为空
  49.         if (StringUtil.isBlank(token)) {
  50.             UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
  51.             //登录成功
  52.             cacheManager.addSecurityContext(userDetails.getToken(), securityContext);
  53.             return;
  54.         }
  55.         //退出或token过期(缓存中设置token过期时间)
  56.         if (SecurityUtil.isNotAuthenticated(securityContext)) {
  57.             cacheManager.deleteSecurityContext(token);
  58.             return;
  59.         }
  60.         //更新Token
  61.         cacheManager.addSecurityContext(token, securityContext);
  62.     }
  63.     @Override
  64.     public boolean containsContext(HttpServletRequest request) {
  65.         //本版本的Spring Security只有SessionManagementFilter中调用该方法
  66.         //已禁用SessionManagementFilter,该方法不会被调用
  67.         String token = request.getHeader(AUTHENTICATION);
  68.         if (StringUtil.isBlank(token)) {
  69.             return false;
  70.         }
  71.         if (StringUtil.isBlank(token)) {
  72.             return false;
  73.         }
  74.         return cacheManager.contains(token);
  75.     }
  76. }
复制代码
四、自定义用户详情UserDetailsImpl
  1. package com.yu.demo.entity;
  2. import lombok.Getter;
  3. import lombok.Setter;
  4. import lombok.ToString;
  5. import org.springframework.security.core.GrantedAuthority;
  6. import org.springframework.security.core.userdetails.UserDetails;
  7. import java.util.Collection;
  8. import java.util.Set;
  9. @Setter
  10. @Getter
  11. @ToString
  12. public class UserDetailsImpl implements UserDetails {
  13.     private String password;
  14.     private final String username;
  15.     private final Set<GrantedAuthority> authorities;
  16.     private final boolean accountNonExpired;
  17.     private final boolean accountNonLocked;
  18.     private final boolean credentialsNonExpired;
  19.     private final boolean enabled;
  20.     /**
  21.      * token
  22.      */
  23.     private String token;
  24.     public UserDetailsImpl(String username, String password, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, boolean enabled, Set<GrantedAuthority> grantedAuthorities) {
  25.         this.username = username;
  26.         this.password = password;
  27.         this.enabled = enabled;
  28.         this.accountNonExpired = accountNonExpired;
  29.         this.credentialsNonExpired = credentialsNonExpired;
  30.         this.accountNonLocked = accountNonLocked;
  31.         this.authorities = grantedAuthorities;
  32.     }
  33.     @Override
  34.     public Collection<? extends GrantedAuthority> getAuthorities() {
  35.         return authorities;
  36.     }
  37.     @Override
  38.     public String getPassword() {
  39.         return password;
  40.     }
  41.     @Override
  42.     public String getUsername() {
  43.         return username;
  44.     }
  45.     /**
  46.      * 账号是否未过期
  47.      *
  48.      * @return true:是,false:否
  49.      */
  50.     @Override
  51.     public boolean isAccountNonExpired() {
  52.         return accountNonExpired;
  53.     }
  54.     /**
  55.      * 账号是否未锁定
  56.      *
  57.      * @return true:是,false:否
  58.      */
  59.     @Override
  60.     public boolean isAccountNonLocked() {
  61.         return accountNonLocked;
  62.     }
  63.     /**
  64.      * 密码是否未过期
  65.      *
  66.      * @return true:是,false:否
  67.      */
  68.     @Override
  69.     public boolean isCredentialsNonExpired() {
  70.         return credentialsNonExpired;
  71.     }
  72.     /**
  73.      * 账号是否启用
  74.      *
  75.      * @return true:是,false:否
  76.      */
  77.     @Override
  78.     public boolean isEnabled() {
  79.         return enabled;
  80.     }
  81. }
复制代码
九、案例源码获取


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

南飓风

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表