【springsecurity6】认证源码分析

概要

接口跳转源码分析

springsecurity6中http://localhost:8080/test/toLogin?username=lkz&pwd=123456的源码跟踪

相关代码

controller层

package com.lkz.controller;

import com.lkz.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class SpringSecurityController {
    @Autowired
    UserService userService;
    @RequestMapping({"/index","/"})
    public String index(int id){
        System.out.println(userService.selectPermsById(id));
        System.out.println(userService.selectNameById(id));
        return "index";
    }

    @RequestMapping("/toLogin")
    public String toLogin(@RequestParam("username") String username, @RequestParam("pwd") String pwd){
        String login = userService.login(username, pwd);
        return "OK";
    }

    @RequestMapping("/level1/{a}")
    public String toLevel1(@PathVariable("a") String a){
        return "views/level1/"+a;
    }

    @RequestMapping("/level2/{b}")
    public String toLevel2(@PathVariable("b") String b){
        return "views/level2/"+b;
    }

    @RequestMapping("/level3/{c}")
    public String toLevel3(@PathVariable("c") String c){
        return "views/level3/"+c;
    }
}

service层

package com.lkz.service;

import com.lkz.mapper.UserMapper;
import com.lkz.pojo.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


public interface UserService {
    public String selectPermsById(int id);
    String selectNameById(@Param("id") int id);
    String selectPwdById(@Param("id") int id);
    User selectUser(@Param("username")String name);
    //登陆逻辑
    String login(String username,String pwd);
}

serviceimpl

package com.lkz.service.impl;

import com.lkz.mapper.UserMapper;
import com.lkz.pojo.User;
import com.lkz.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;
    private final AuthenticationManager authenticationManager;

    public UserServiceImpl(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public String selectPermsById(int id) {
        String perms = userMapper.selectPermsById(id);
        return perms;
    }

    @Override
    public String selectNameById(int id) {
        String username = userMapper.selectNameById(id);
        return username;
    }

    public String selectPwdById(int id) {
        String pwd = userMapper.selectPwdById(id);
        return pwd;
    }
    public User selectUser(String name) {
        User user= userMapper.selectUser(name);
        return user;
    }
    public String login(String username,String pwd){
        //传入用户名和密码
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, pwd);
        //实现登陆逻辑,此时就会去调用loadUserByUsername
        //authenticate其实就是UserDetails(authentication前台拿到的user对象)
        Authentication authenticate = authenticationManager.authenticate(authentication);
        log.info("authenticate========="+authenticate);
        return "OKKKKK";
    }
}

userDetailserviceimpl获得数据库对象的方法实现类

package com.lkz.service.impl;

import com.lkz.mapper.UserMapper;
import com.lkz.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.List;

/**
 * UserDetails:得出库里的数据
 */
@Service
@Slf4j
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User user = userMapper.selectUser(username);
            if (user == null) {
                throw new UsernameNotFoundException("User not found with username: " + username);
            }
            log.info("user======"+user.getPwd());
        List<GrantedAuthority> atuhs = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getPerms());
            return new org.springframework.security.core.userdetails.User(
                    user.getUsername(),
                    user.getPwd(),
                    atuhs
                    /* Add user roles/authorities here */);
        }
}


源码跳转

核心主要是从login处的源码追踪

所涉及到的主要方法:

  • login:controller跳转进入的登陆逻辑方法
  • authenticate:login方法中总的认证入口方法
  • provider.authenticate(authentication):拿着前台传入的用户名密码对象authentication进行认证
  • retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication):检索user对象【打算调用数据库查的方法了,所以必须要传入用户名,以及前台对象authentication】
  • this.getUserDetailsService().loadUserByUsername(username):计划调用自写的实现类的方法调数据库查user对象
  • additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication):将user对象【库里的】和前台接收到组合后的待比对user对象【authentication】
  • this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())):进入比对方法
  • BCrypt.checkpw(rawPassword.toString(), encodedPassword):将前台明文和库里密文传入,获取盐进行加密比对【equalsNoEarlyReturn】
  • equalsNoEarlyReturn(hashed, hashpwforcheck(passwordb, hashed)):将库里取出的密文和前台接受到的明文加密后比对

源码中的核心代码

authenticate:login方法中总的认证入口方法

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		int currentPosition = 0;
		int size = this.providers.size();
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
						provider.getClass().getSimpleName(), ++currentPosition, size));
			}
			try {
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException ex) {
				prepareException(ex, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw ex;
			}
			catch (AuthenticationException ex) {
				lastException = ex;
			}
		}
		if (result == null && this.parent != null) {
			// Allow the parent to try.
			try {
				parentResult = this.parent.authenticate(authentication);
				result = parentResult;
			}
			catch (ProviderNotFoundException ex) {
				// ignore as we will throw below if no other exception occurred prior to
				// calling parent and the parent
				// may throw ProviderNotFound even though a provider in the child already
				// handled the request
			}
			catch (AuthenticationException ex) {
				parentException = ex;
				lastException = ex;
			}
		}
		if (result != null) {
			if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}
			// If the parent AuthenticationManager was attempted and successful then it
			// will publish an AuthenticationSuccessEvent
			// This check prevents a duplicate AuthenticationSuccessEvent if the parent
			// AuthenticationManager already published it
			if (parentResult == null) {
				this.eventPublisher.publishAuthenticationSuccess(result);
			}

			return result;
		}

		// Parent was null, or didn't authenticate (or throw an exception).
		if (lastException == null) {
			lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
					new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
		}
		// If the parent AuthenticationManager was attempted and failed then it will
		// publish an AbstractAuthenticationFailureEvent
		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the
		// parent AuthenticationManager already published it
		if (parentException == null) {
			prepareException(lastException, authentication);
		}
		throw lastException;
	}

provider.authenticate(authentication):拿着前台传入的用户名密码对象authentication进行认证 AbstractUserDetailsAuthenticationProvider实现类的

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

retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication):检索user对象【打算调用数据库查的方法了,所以必须要传入用户名,以及前台对象authentication】

	@Override
	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException(
						"UserDetailsService returned null, which is an interface contract violation");
			}
			return loadedUser;
		}
		catch (UsernameNotFoundException ex) {
			mitigateAgainstTimingAttack(authentication);
			throw ex;
		}
		catch (InternalAuthenticationServiceException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
	}

返回到AbstractUserDetailsAuthenticationProvider实现类中,继续执行additionalAuthenticationChecks

		try {
			this.preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}

AbstractUserDetailsAuthenticationProvider抽象类-->DaoAuthenticationProvider实现类中的additionalAuthenticationChecks

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

matches核心方法,调PasswordEncoder接口--->BCryptPasswordEncoder实现类的

	@Override
	public boolean matches(CharSequence rawPassword, String encodedPassword) {
		if (rawPassword == null) {
			throw new IllegalArgumentException("rawPassword cannot be null");
		}
		if (encodedPassword == null || encodedPassword.length() == 0) {
			this.logger.warn("Empty encoded password");
			return false;
		}
		if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
			this.logger.warn("Encoded password does not look like BCrypt");
			return false;
		}
		return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
	}

BCrypt.checkpw

	public static boolean checkpw(String plaintext, String hashed) {
		byte[] passwordb = plaintext.getBytes(StandardCharsets.UTF_8);
		return equalsNoEarlyReturn(hashed, hashpwforcheck(passwordb, hashed));
	}

equalsNoEarlyReturn

	static boolean equalsNoEarlyReturn(String a, String b) {
		return MessageDigest.isEqual(a.getBytes(StandardCharsets.UTF_8), b.getBytes(StandardCharsets.UTF_8));
	}

最后会执行到login中总的认证方法中,result = provider.authenticate(authentication);不为空,返回的是result对象
reslut
认证成功
在这里插入图片描述
后台会有显示已认证
在这里插入图片描述

小结

核心就是比对密码,要注意数据库中放密文

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值