首页 > 其他分享 >七、Spring Boot集成Spring Security之前后分离认证最佳实现

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

时间:2024-11-06 22:48:38浏览次数:5  
标签:web Spring Boot springframework token import org Security 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-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

  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;
    }

    @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

  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;
    }

    @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

  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;

    @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;
    }
}

五、自定义用户详情数据库查询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;


    @Override
    public 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只是实现认证流程,未使用jwt
        userDetails.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 {

    @Override
    public 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);
    }

    @Override
    public 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";

    @Autowired
    private LoginResultHandler loginResultHandler;
    @Autowired
    private SecurityContextRepository securityContextRepository;

    @Bean
    public 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>

九、案例源码获取

标签:web,Spring,Boot,springframework,token,import,org,Security,security
From: https://blog.csdn.net/dhbfjh/article/details/143575392

相关文章

  • Spring学习记录02
    IoC/DI配置第三方bean案例:数据源对象的管理管理alibaba的druid数据源新建一个工程,结构如下                                        首先需要在pro.xml文件中配置druid对象,添加相关依赖<dependency><groupId>c......
  • 109 基于springboot+vue校内店铺网上订单小程序
    项目介绍校内店铺网上订单小程序分为客户端和服务端登录模块,客户端主要是用户进行登录,用户在校内店铺网上订单小程序中进行注册、登录,可以修改个人信息,还可以查看商品信息,对商品信息进行收藏、评论、立即订购、加入购物车等操作。管理员在服务端可以修改个人信息和密码,可以......
  • 基于微信小程序的智慧停车系统设计与实现(源码+springboot+uinapp+部署文档+讲解等)
    收藏关注不迷路!!......
  • 2025最新-计算机毕业设计java基于Springboot的智慧教学平台的系统设计与实现
    一、项目介绍  基于SpringBoot的智慧教学平台系统设计与实现是一个复杂但充满挑战的项目,旨在通过现代软件开发技术优化教育流程,提升教学质量和管理效率。以下是对该系统的详细介绍:智慧教学平台是一款集成了多种功能的综合性教育管理系统,通过运用SpringBoot框架、MySQ......
  • 基于SpringBoot+Vue的学生档案管理系统(源码+LW+调试文档+讲解)
    背景及意义目的档案信息整合与存储:基于SpringBoot+Vue的学生档案管理系统,目的是将学生的各类档案信息进行整合和存储。包括学生的个人基本信息(姓名、性别、出生日期、民族、籍贯等)、学业信息(入学成绩、每学期各科成绩、考试排名、奖惩情况等)、家庭背景信息(家庭成员、......
  • springboot毕设 咖啡店运营与管理系统程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在快节奏的现代生活中,咖啡店作为人们休闲放松、社交互动的重要场所,其运营与管理效率直接影响到顾客体验与经营效益。随着信息技术的发展,传统的人工管......
  • springboot毕设 考点周边酒店预订系统 程序+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着教育事业的蓬勃发展和各类考试的频繁举行,考点周边的住宿需求急剧增加。尤其是在大型考试如高考、研究生入学考试期间,考生及其家长对考点周边酒店......
  • 基于Java+SpringBoot心理测评心理测试系统功能实现三
    一、前言介绍:1.1项目摘要心理测评和心理测试系统在当代社会中扮演着越来越重要的角色。随着心理健康问题日益受到重视,心理测评和心理测试系统作为评估个体心理状态、诊断心理问题、制定心理治疗方案的工具,其需求和应用范围不断扩大。首先,现代社会节奏快速,竞争激烈,人们面临着来......
  • 七、Spring Boot集成Spring Security之前后分离认证最佳实现
    二、自定义用户名密码认证过滤器RestfulUsernamePasswordAuthenticationFilter1、注册过滤器方式使用httpSecurity.addFilter/addFilterBefore/addFilterAfter向过滤器链中添加过滤器,其中addFilter只能添加内置的过滤器,顺序已在过滤器顺序注册器(FilterOrderRegistration)中设置;a......
  • 基于Java+SpringBoot心理测评心理测试系统功能实现四
    一、前言介绍:1.1项目摘要心理测评和心理测试系统在当代社会中扮演着越来越重要的角色。随着心理健康问题日益受到重视,心理测评和心理测试系统作为评估个体心理状态、诊断心理问题、制定心理治疗方案的工具,其需求和应用范围不断扩大。首先,现代社会节奏快速,竞争激烈,人们面临着来......