项目mall学习——SpringSecurity+JWT实现登录认证

本文结合macro大神mall项目以及以下三篇优秀文章总结而成:
(1)Spring Security 工作原理概览
(2)spring security——基本介绍(一)
(3)Security身份认证之UserDetailsService

一、SpringSecurity过滤器链

用户发送请求会经过Security过滤器链,通过UsernamePassword
AuthenticationFilter时检查username和password是否在数据库中。
在这里插入图片描述

二、身份认证流程图

在这里插入图片描述

三、身份认证代码跟踪

1.AbstractAuthenticationProcessingFilter

身份认证时首先经过该过滤器执行doFilter方法,其中 调用 requiresAuthentication方法判断是否需要验证,如需要则会调用验证方法(核心)attemptAuthentication,此时有三种结果:

  1. 返回Null,表示身份验证不完整。假设子类做了一些必要的工作(如重定向)来继续处理验证,方法将立即返回。假设后一个请求将被这种方法接收,其中返回的Authentication对象不为空。
  2. 返回一个 Authentication 对象。校验成功,调用successfulAuthentication方法。
  3. 验证时发生 AuthenticationException。验证失败,调用unsuccessfulAuthentication方法。

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
	//......
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        if (!this.requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
        } else {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Request is to process authentication");
            }

            Authentication authResult;
            try {
                authResult = this.attemptAuthentication(request, response);
                if (authResult == null) {
                    return;
                }

                this.sessionStrategy.onAuthentication(authResult, request, response);
            } catch (InternalAuthenticationServiceException var8) {
                this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
                this.unsuccessfulAuthentication(request, response, var8);
                return;
            } catch (AuthenticationException var9) {
                this.unsuccessfulAuthentication(request, response, var9);
                return;
            }

            if (this.continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }

            this.successfulAuthentication(request, response, chain, authResult);
        }
    }
    //......
}

2.UsernamePasswordAuthenticationFilter

由上一步可知,校验核心在于调用了attemptAuthentication方法,但是AbstractAuthenticationProcessingFilter类中并没有实现,而是交由其子类UsernamePasswordAuthenticationFilter实现。

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
	//......
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
    //......
}

发现该方法将request中提取的username和password封装成了token,并调用了getAuthenticationManager方法,委托AuthenticationManager接口进行验证(authenticate方法)。

3.ProviderManager

默认情况下注入 Spring 容器的 AuthenticationManager 是 ProviderManager。ProviderManager实现了AuthenticationManager接口,。ProviderManager管理了许多AuthenticationProvider (接口),其验证逻辑是通过getProviders方法得到一系列AuthenticationProvider ,进行验证(authenticate方法),直到有一个验证成功,不继续验证。不难看出其核心代码是

Authentication result = provider.authenticate(authentication);
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
	//......
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //......
        Iterator var8 = this.getProviders().iterator();

        while(var8.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var8.next();
            if (provider.supports(toTest)) {
                if (debug) {
                    logger.debug("Authentication attempt using " + provider.getClass().getName());
                }

                try {
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (InternalAuthenticationServiceException | AccountStatusException var13) {
                    this.prepareException(var13, authentication);
                    throw var13;
                } catch (AuthenticationException var14) {
                    lastException = var14;
                }
            }
        }
		//......
    }
    //......
}

4.AbstractUserDetailsAuthenticationProvider

AbstractUserDetailsAuthenticationProvider类实现了AuthenticationProvider,实现了authenticate方法。

public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
	//......
	
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
            return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
        });
        String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("User '" + username + "' not found");
                if (this.hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }

                throw var6;
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }
		//......
    }
    //......
}

其核心代码为:

UserDetails user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);

该方法交由其子类DaoAuthenticationProvider实现。

5.DaoAuthenticationProvider

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
	//......
	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);
        }
    }
	//......
}

其核心代码为:

UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

发现调用了UserDetailsService接口进行具体身份验证逻辑,用户可以自定义配置其实现类以及UserDetails的实现类(拓展除了username和password以外的其他字段,如permissions、roles)。

四、自定义实现并配置UserDetailsService接口

1.自定义实现与安全配置

  • configure(HttpSecurity httpSecurity):用于配置需要拦截的url路径、jwt过滤器及出异常后的处理器;
  • configure(AuthenticationManagerBuilder auth):用于配置UserDetailsService及PasswordEncoder;
  • UserDetailsService:SpringSecurity定义的核心接口,用于根据用户名获取用户信息,需要自行实现;
  • UserDetails:SpringSecurity定义用于封装用户信息的类(主要是用户信息和权限),需要自行实现;
  • JwtAuthenticationTokenFilter:在用户名和密码校验前添加的过滤器,如果有jwt的token,会自行根据token信息进行登录,可校验token是否过期。
  • UserDetailsService中使用了业务层逻辑,详情请自行学习macro大神mall项目。
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	//......
	@Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
    // 自定义权限拦截器JWT过滤器
    registry.
    .and()
    .addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    //......
    }
	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder());
    }
    @Bean
    public UserDetailsService userDetailsService() {
        //获取登录用户信息
        return username -> {
            UmsAdmin admin = adminService.getAdminByUsername(username);
            if (admin != null) {
                List<UmsPermission> permissionList = adminService.getPermissionList(admin.getId());
                return new AdminUserDetails(admin,permissionList);
            }
            throw new UsernameNotFoundException("用户名或密码错误");
        };
    }
	//......
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值