• 01-SpringSecurity认证流程


    一、工作原理

    SpringSecurity是使用filter来对资源进行保护的,当初始化SpringSecurity时会创建一个 SpringSecurityFilterChain的过滤器链,它实现了servlet的filter,因此外部的请求会经过该链
    在这里插入图片描述
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/7880712652094db98577c48ba9ff66ee.png

    默认状态下,这个filterChain是如下结构
    在这里插入图片描述
    在这里插入图片描述

    二、认证流程

    在这里插入图片描述
    从上面的流程中我们可以看到几个重要的组件

    1. authenticationFilter:过滤器,将请求携带的参数封装成指定的token传给authenticationManager,可以为它设定成功和失败的处理器
    2. AuthenticationManager:认证管理,对项目中的AuthenticationProvider进行管理
    3. DaoAuthenticationProvider:自定义业务逻辑,对指定类型的token进行校验
    4. UserDetailsService:查询数据库中的用户信息,provider利用查询到的信息和token进行比对,决定是否认证成功
    5. SecurityContextHolder:通过认证的authentication会保存到安全上下文中,后续SpringSecurity需要校验权限时,会根据这个authentication来校验

    三、组件

    (一)token

    token是进行认证所需要的凭证,不同的authenticationProvider支持的token不一样,所以不同的认证方式同样对应着不同的token;比如使用账号密码和手机验证码使用的token就不一样

    SpringSecurity为我们定义好了authentication的接口
    Authentication

    public interface Authentication extends Principal, Serializable {
        Collection getAuthorities();
    
        Object getCredentials();
    
        Object getDetails();
    
        Object getPrincipal();
    
        boolean isAuthenticated();
    
        void setAuthenticated(boolean var1) throws IllegalArgumentException;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    UsernamePasswordAuthenticationToken(框架提供的)

    public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
        private static final long serialVersionUID = 530L;
        private final Object principal;
        private Object credentials;
    
        public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
            super((Collection)null);
            this.principal = principal;
            this.credentials = credentials;
            this.setAuthenticated(false);
        }
    
        public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection authorities) {
            super(authorities);
            this.principal = principal;
            this.credentials = credentials;
            super.setAuthenticated(true);
        }
    
        public Object getCredentials() {
            return this.credentials;
        }
    
        public Object getPrincipal() {
            return this.principal;
        }
    
        public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
            if (isAuthenticated) {
                throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
            } else {
                super.setAuthenticated(false);
            }
        }
    
        public void eraseCredentials() {
            super.eraseCredentials();
            this.credentials = null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    PhoneCodeAuthenticationToken(自定义token)

    public class PhoneCodeAuthenticationToken extends AbstractAuthenticationToken {
    
        List authorities;
    
        Object credentials;
    
        Object details;
    
        Object principal;
    
        public PhoneCodeAuthenticationToken(Object principal, Object credentials) {
            super((Collection)null);
            this.principal = principal;
            this.credentials = credentials;
            this.setAuthenticated(false);
        }
    
        public PhoneCodeAuthenticationToken(Object principal, Object credentials, Collection authorities) {
            super(authorities);
            this.principal = principal;
            this.credentials = credentials;
            super.setAuthenticated(true);
        }
    
        @Override
        public Object getCredentials() {
            return credentials;
        }
    
        @Override
        public Object getPrincipal() {
            return principal;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    (二)UserDetailsService

    它的作用是查询用户信息,封装成UserDetais的实现类返回,在authenticationProvider中被使用到

    官方定义接口

    public interface UserDetailsService {
        UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
    }
    
    • 1
    • 2
    • 3

    UserDetails(框架提供)

    public interface UserDetails extends Serializable {
        Collection getAuthorities();
    
        String getPassword();
    
        String getUsername();
    
        boolean isAccountNonExpired();
    
        boolean isAccountNonLocked();
    
        boolean isCredentialsNonExpired();
    
        boolean isEnabled();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    主要包括username、password、authorities三部分,用来表达当前用户的信息,当然我们可以使用User(框架提供)来构建UserDetails实现类也可以自定义UserDetails的实现类来包含更多的信息

    (三)authenticationProvider

    该类用来写token的认证逻辑

    接口定义

    public interface AuthenticationProvider {
    	//认证逻辑
        Authentication authenticate(Authentication var1) throws AuthenticationException;
    	
    	//支持token类型
        boolean supports(Class var1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    短信登录provider

    public class SMSAuthenticationProvider implements AuthenticationProvider {
    
        public static Map codeMap = new HashMap<>();
    
        private UserDetailsService userDetailsService;
    
    
        public SMSAuthenticationProvider(UserDetailsService userDetailsService){
            this.userDetailsService = userDetailsService;
        }
        /**
         *
         * @param authentication
         * @return
         * @throws AuthenticationException
         */
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            String phone = (String) authentication.getPrincipal();
            String code = (String) authentication.getCredentials();
            String cacheCode = codeMap.get(phone);
            if(cacheCode == null || !code.equals(cacheCode)){
                throw new InternalAuthenticationServiceException(
                        "code error");
            }
    
            SMSUserDetais details = (SMSUserDetais) userDetailsService.loadUserByUsername(phone);
            if(details == null){
                throw new UsernameNotFoundException("not found this phone");
            }
    
            List authorities = new ArrayList<>();
            return new PhoneCodeAuthenticationToken(details.getUsername(),details.getPassword(),authorities);
        }
    
        @Override
        public boolean supports(Class authentication) {
            return PhoneCodeAuthenticationToken.class.isAssignableFrom(authentication);
        }
    
        public void setUserDetailsService(UserDetailsService userDetailsService){
            this.userDetailsService = userDetailsService;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    DaoAuthenticationProvider

    框架本身也提供了一个provider,针对UsernamePasswordAuthenticationToken

    public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
        private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
        private PasswordEncoder passwordEncoder;
        private volatile String userNotFoundEncodedPassword;
        private UserDetailsService userDetailsService;
        private UserDetailsPasswordService userDetailsPasswordService;
    
        public DaoAuthenticationProvider() {
            this.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
        }
    
        protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
            if (authentication.getCredentials() == null) {
                this.logger.debug("Authentication failed: no credentials provided");
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            } else {
                String presentedPassword = authentication.getCredentials().toString();
                if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
                    this.logger.debug("Authentication failed: password does not match stored value");
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }
            }
        }
    
        protected void doAfterPropertiesSet() {
            Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
        }
    
        protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
            this.prepareTimingAttackProtection();
    
            try {
                UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
                if (loadedUser == null) {
                    throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
                } else {
                    return loadedUser;
                }
            } catch (UsernameNotFoundException var4) {
                this.mitigateAgainstTimingAttack(authentication);
                throw var4;
            } catch (InternalAuthenticationServiceException var5) {
                throw var5;
            } catch (Exception var6) {
                throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
            }
        }
    
        protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
            boolean upgradeEncoding = this.userDetailsPasswordService != null && this.passwordEncoder.upgradeEncoding(user.getPassword());
            if (upgradeEncoding) {
                String presentedPassword = authentication.getCredentials().toString();
                String newPassword = this.passwordEncoder.encode(presentedPassword);
                user = this.userDetailsPasswordService.updatePassword(user, newPassword);
            }
    
            return super.createSuccessAuthentication(principal, authentication, user);
        }
    
        private void prepareTimingAttackProtection() {
            if (this.userNotFoundEncodedPassword == null) {
                this.userNotFoundEncodedPassword = this.passwordEncoder.encode("userNotFoundPassword");
            }
    
        }
    
        private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
            if (authentication.getCredentials() != null) {
                String presentedPassword = authentication.getCredentials().toString();
                this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
            }
    
        }
    
        public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
            Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
            this.passwordEncoder = passwordEncoder;
            this.userNotFoundEncodedPassword = null;
        }
    
        protected PasswordEncoder getPasswordEncoder() {
            return this.passwordEncoder;
        }
    
        public void setUserDetailsService(UserDetailsService userDetailsService) {
            this.userDetailsService = userDetailsService;
        }
    
        protected UserDetailsService getUserDetailsService() {
            return this.userDetailsService;
        }
    
        public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
            this.userDetailsPasswordService = userDetailsPasswordService;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96

    (四)filter

    对符合条件的url进行过滤,框架提供了一个UsernamePasswordAuthenticationFilter处理账号密码登录,下面我们自定义一个处理验证码登录的filter

    短信登录Filter

    /**
     * 短信登录过滤器
     * @author liqi
     */
    public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    
        public static final String SMS_MOBILE = "phone";
    
        public static final String CODE = "code";
    
    
    
        public SmsAuthenticationFilter() {
            super(new AntPathRequestMatcher("/login/sms", "POST"));
        }
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
                throws AuthenticationException {
            String loginName = request.getParameter(SMS_MOBILE);
            if (StringUtils.isEmpty(loginName)) {
                throw new AuthenticationServiceException("手机号不能为空");
            }
            String code = request.getParameter(CODE);
            if (StringUtils.isEmpty(code)) {
                throw new AuthenticationServiceException("手机验证码不能为空");
            }
    
    
            PhoneCodeAuthenticationToken authRequest = new PhoneCodeAuthenticationToken(loginName,code);
            authRequest.setDetails(super.authenticationDetailsSource.buildDetails(request));
            System.out.println(authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    配置该filter

    SecurityConfigurerAdapter securityConfigurerAdapter = new SecurityConfigurerAdapter() {
    			@Override
    			public void configure(HttpSecurity httpSecurity) throws Exception {
    				// 手机号+短信登录
    				AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
    				AbstractAuthenticationProcessingFilter smsFilter = new SmsAuthenticationFilter();
    				smsFilter.setAuthenticationManager(authenticationManager);
    				smsFilter.setAuthenticationSuccessHandler(successHandler);
    				smsFilter.setAuthenticationFailureHandler(failureHandler);
    				httpSecurity.addFilterBefore(smsFilter, UsernamePasswordAuthenticationFilter.class);
    			}
    		};
    		http.apply(securityConfigurerAdapter);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    四、整体配置

    package com.codexie.security.config;
    
    import com.codexie.security.filter.SmsAuthenticationFilter;
    import com.codexie.security.provider.SMSAuthenticationProvider;
    import com.codexie.security.service.impl.UserDetailsServiceImpl;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
    import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.factory.PasswordEncoderFactories;
    import org.springframework.security.crypto.password.NoOpPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.DefaultSecurityFilterChain;
    import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    @Configuration
    @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    	@Autowired
    	UserDetailsServiceImpl userDetailsService;
    
    	@Autowired
    	SuccessHandler successHandler;
    
    	@Autowired
    	DeniedHandler deniedHandler;
    
    	@Autowired
    	FailureHandler failureHandler;
    
    
    	public SecurityConfig() {
    	}
    
    
    
    	@Bean
    	public SMSAuthenticationProvider smsAuthenticationProvider(){
    		SMSAuthenticationProvider provider = new SMSAuthenticationProvider(userDetailsService);
    		return provider;
    	}
    
    	/**
    	 * 添加自定义认证方式
    	 * @param auth
    	 * @throws Exception
    	 */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        	//将自定义的provider添加至list
    		auth.authenticationProvider(smsAuthenticationProvider())
    				//设置内置的provider的userDetailsService以及passwordEncoder
    		 		.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
        }
        
    
        //不定义没有password grant_type
        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Bean
    	public DaoAuthenticationProvider daoAuthenticationProvider(){
    		DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
    		daoAuthenticationProvider.setUserDetailsService(userDetailsService);
    		daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
    		return daoAuthenticationProvider;
    	}
        
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers("/public/**", "/webjars/**", "/v2/**", "/swagger**", "/static/**", "/resources/**");
    		//web.httpFirewall(new DefaultHttpFirewall());//StrictHttpFirewall 去除验url非法验证防火墙
        }
    
        @Bean
    	public PasswordEncoder passwordEncoder(){
    		return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    	}
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
    
    		http.authorizeRequests()
    			.antMatchers("/login*").permitAll()
    			.antMatchers("/login/code").permitAll()
    			.antMatchers("/logout*").permitAll()
    			.antMatchers("/druid/**").permitAll()
    			.anyRequest().authenticated()
    			.and().formLogin()
    				.loginPage("/login") // 登录页面
    				.loginProcessingUrl("/login.do") // 登录处理url
    				.failureUrl("/login?authentication_error=1")
    				.defaultSuccessUrl("/main")
    				.usernameParameter("username")
    				.passwordParameter("password")
    			.and().logout()
    				.logoutUrl("/logout.do")
    				.deleteCookies("JSESSIONID")
    				.logoutSuccessUrl("/")
    			.and().csrf().disable()
    			.exceptionHandling()
    			.accessDeniedPage("/login?authorization_error=2");
    
    
    		SecurityConfigurerAdapter securityConfigurerAdapter = new SecurityConfigurerAdapter() {
    			@Override
    			public void configure(HttpSecurity httpSecurity) throws Exception {
    				// 手机号+短信登录
    				AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
    				AbstractAuthenticationProcessingFilter smsFilter = new SmsAuthenticationFilter();
    				smsFilter.setAuthenticationManager(authenticationManager);
    				smsFilter.setAuthenticationSuccessHandler(successHandler);
    				smsFilter.setAuthenticationFailureHandler(failureHandler);
    				httpSecurity.addFilterBefore(smsFilter, UsernamePasswordAuthenticationFilter.class);
    			}
    		};
    		http.apply(securityConfigurerAdapter);
        }
        
        
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
  • 相关阅读:
    【数据结构】动态规划:如何通过最优子结构,完成复杂问题求解
    POLARDB IMCI 白皮书 云原生HTAP 数据库系统 一 数据压缩打更新 (本篇有数据到列节点异步但不延迟的解释)...
    设计模式简要总结
    【开发教程11】疯壳·开源蓝牙心率防水运动手环-整机功能代码讲解
    Python中的字典(Dictionary)学习
    如何扩展及优化CI/CD流水线?
    customRef
    Spring Cloud Function Spel表达式注入
    【机器学习】21天挑战赛学习笔记(三)
    【QT 5 +Linux下软件qt软件打包+qt生成软件创建可以安装压缩包+学习他人文章+第三篇:学习打包】
  • 原文地址:https://blog.csdn.net/qq_42861526/article/details/126127876