【无标题】

formLogin的方式,指定登录接口url以及登陆成功后的handler,handler类把token返回给前端

        http.formLogin()
                .loginProcessingUrl("/api/login")
                .successHandler((httpServletRequest, httpServletResponse, authentication) -> {
                    httpServletResponse.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                    String username = httpServletRequest.getParameter("username");
                    String password = httpServletRequest.getParameter("password");

                    log.info("登录成功, 用户名:{}, 密码:{}", username, password);

                    //设置cookie 24小时过期
                    HttpSession session = httpServletRequest.getSession();
                    session.setMaxInactiveInterval(60 * 60 * 24);

                    httpServletResponse.getWriter().write(JSONUtil.toJsonStr(ResponseResult.success(session.getId())));
                })

大致流程:

1、先在 AbstractAuthenticationProcessingFilter 中的requiresAuthentication方法判断当前请求是不是登录请求(SecurityConfiguration里面配置的formLogin的路径)

2、是登录请求的话,然后在这个类UsernamePasswordAuthenticationFilter的attemptAuthentication方法里面调用UsernamePasswordAuthenticationToken的二参构造把usernamepassword传到UsernamePasswordAuthenticationToken对象中(principal就是username,credentials就是password),这个时候还是未认证成功的状态,然后调用authenticate方法进行登录认证

UsernamePasswordAuthenticationToken authenticationToken = 
new UsernamePasswordAuthenticationToken(username, password);

this.getAuthenticationManager().authenticate(authRequest);

3、authenticate方法由子类ProviderManager来实现,子类根据传递的参数类型选择对应的provider执行

for (AuthenticationProvider provider : getProviders()) {
    if (!provider.supports(toTest)) {
		continue;
	}
    result = provider.authenticate(authentication);
}

4、自定义一个provider需要继承DaoAuthenticationProvider并且实现supports方法(authenticate方法也可以选择自己实现)

不重写authenticate方法的话,最终是调用AbstractUserDetailsAuthenticationProvider的authenticate方法

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));
		String username = determineUsername(authentication);
		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);
		if (user == null) {
			cacheWasUsed = false;
			try {
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException ex) {
				this.logger.debug("Failed to find user '" + username + "'");
				if (!this.hideUserNotFoundExceptions) {
					throw ex;
				}
				throw new BadCredentialsException(this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
			}
			Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
		}
		try {
			this.preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException ex) {
			if (!cacheWasUsed) {
				throw ex;
			}
			// There was a problem, so try again after checking
			// we're using latest data (i.e. not from the cache)
			cacheWasUsed = false;
			user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			this.preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		this.postAuthenticationChecks.check(user);
		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}
		Object principalToReturn = user;
		if (this.forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

5、其中retrieveUser方法调用的是DaoAuthenticationProvider的方法,最终是调用自己实现好的loadUserByUsername方法(实现UserDetailsService接口,重写该方法,根据用户名查询用户信息)

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

additionalAuthenticationChecks方法调用的也是DaoAuthenticationProvider的方法,比对密码的逻辑就在这里(也可以自己重写该方法),密码加密方式需要在SecurityConfiguration中指定,不指定的话默认使用的是bcrypt

@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
		UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
	if (authentication.getCredentials() == null) {
		this.logger.debug("Failed to authenticate since no credentials provided");
		throw new BadCredentialsException(this.messages
				.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
	}
	String presentedPassword = authentication.getCredentials().toString();
	if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
		this.logger.debug("Failed to authenticate since password does not match stored value");
		throw new BadCredentialsException(this.messages
				.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
	}
}

6、密码比对成功后将会调用createSuccessAuthentication方法,最终是调用了UsernamePasswordAuthenticationToken类的三参构造,这时才是认证成功的authentication对象

UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
      authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));

7、上面是认证逻辑,认证成功之后在attemptAuthentication方法之后把登陆成功的用户信息写入SecurityContext

this.sessionStrategy.onAuthentication(authenticationResult, request, response);

8、然后在后面的SecurityContextPersistenceFilter中,通过这行SecurityContextRepository.saveContext方法保存session

this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());

最终是调用的setAttribute方法保存到数据库(JdbcSession)或者内存(MapSession)中去

httpSession.setAttribute(springSecurityContextKey, context);

9、最后调用登录成功后的处理类,把token返回给前端

10、第二次请求过来时,在请求头携带token,请求头的headerName需要自己指定

@Bean
public HttpSessionIdResolver httpSessionIdResolver() {
    return new HeaderHttpSessionIdResolver("access-token");
}

SecurityContextPersistenceFilter这个过滤器中,调用这行

SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);

HttpSessionSecurityContextRepository.loadContext方法

@Override
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
	HttpServletRequest request = requestResponseHolder.getRequest();
	HttpServletResponse response = requestResponseHolder.getResponse();
	HttpSession httpSession = request.getSession(false);
	SecurityContext context = readSecurityContextFromSession(httpSession);
	if (context == null) {
		context = generateNewContext();
		if (this.logger.isTraceEnabled()) {
			this.logger.trace(LogMessage.format("Created %s", context));
		}
	}
	SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(response, request,
			httpSession != null, context);
	requestResponseHolder.setResponse(wrappedResponse);
	requestResponseHolder.setRequest(new SaveToSessionRequestWrapper(request, wrappedResponse));
	return context;
}

通过request.getSession方法,根据请求头中的token获得session

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值