SpringBoot+MyBatis-plus+SpringSecurity+JWT verification code authentication

This article is based on SpringBoot+MyBatis-plus+SpringSecurity+JWT login authentication, and realizes the login authentication function with additional verification code on the basis of separation of front and back ends .

1. The core code of springsecurity verification code mode

Create a verification code filter

Add VerificationCodeLoginFilter to the filter. What is filtered here is the request of "/auth/code", and different request addresses are used to distinguish different login authentication methods.

package com.digipower.sercurity.filter;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.digipower.common.entity.Result;
import com.digipower.sercurity.entity.JwtUserDetails;
import com.digipower.sercurity.token.VerificationCodeAuthenticationToken;
import com.digipower.sercurity.util.JwtTokenUtil;
import com.fasterxml.jackson.databind.ObjectMapper;

public class VerificationCodeLoginFilter extends AbstractAuthenticationProcessingFilter {
	public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
	public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
	public static final String SPRING_SECURITY_FORM_CODE_KEY = "code";

	private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
	private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
	private String codeParameter = SPRING_SECURITY_FORM_CODE_KEY;
	

	/**
	 * 是否仅 POST 方式
	 */
	private boolean postOnly = true;

	 /**
     * 获取授权管理, 创建VerificationCodeLoginFilter时获取
     */
    private AuthenticationManager authenticationManager;
	

	public VerificationCodeLoginFilter(String defaultFilterProcessesUrl,AuthenticationManager authenticationManager) {
	
		super(new AntPathRequestMatcher(defaultFilterProcessesUrl, "POST"));
		this.authenticationManager = authenticationManager;
	}

	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException, IOException, ServletException {
		// TODO Auto-generated method stub
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
		}

		String username = obtainUsername(request);
		String password = obtainPassword(request);
		String code = obtainCode(request);

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

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

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

		username = username.trim();
		code = code.trim();

		VerificationCodeAuthenticationToken authRequest = new VerificationCodeAuthenticationToken(username, password,
				code);

		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);

		return authenticationManager.authenticate(authRequest);
	}

	protected String obtainPassword(HttpServletRequest request) {
		return request.getParameter(passwordParameter);
	}

	protected String obtainUsername(HttpServletRequest request) {
		return request.getParameter(usernameParameter);
	}

	protected String obtainCode(HttpServletRequest request) {
		return request.getParameter(codeParameter);
	}

	protected void setDetails(HttpServletRequest request, VerificationCodeAuthenticationToken authRequest) {
		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
	}

	/**
	 * TODO 一旦调用 springSecurity认证登录成功,立即执行该方法
	 *
	 * @param request
	 * @param response
	 * @param chain
	 * @param authResult
	 * @throws IOException
	 * @throws ServletException
	 */
	@Override
	protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
			Authentication authResult) throws IOException {

		// 生成jwt,并返回
		VerificationCodeAuthenticationToken token = (VerificationCodeAuthenticationToken)authResult;
		JwtUserDetails userEntity = new JwtUserDetails();
		if(token.getPrincipal() != null){
			userEntity.setUserName(String.valueOf(token.getPrincipal()));
		}
		if(token.getAuthorities() != null && token.getAuthorities().size() > 0){
			userEntity.setAuthorities(token.getAuthorities());
		}
		if(token.getCredentials() != null){
			userEntity.setPassword(String.valueOf(token.getCredentials()));
		}
		String jwtToken = JwtTokenUtil.generateToken(userEntity);

		ObjectMapper objectMapper = new ObjectMapper();
		response.setContentType("application/json;charset=UTF-8");
		ServletOutputStream out = response.getOutputStream();
		String str = objectMapper.writeValueAsString(Result.ok().setDatas("token", jwtToken));
		out.write(str.getBytes("UTF-8"));
		out.flush();
		out.close();
	}

	/**
	 * 认证失败,改方法被调用
	 */
	@Override
	protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException ex) throws IOException, ServletException {
		ObjectMapper objectMapper = new ObjectMapper();
		try {
			if (ex instanceof UsernameNotFoundException || ex instanceof BadCredentialsException) {
				response.setContentType("application/json;charset=UTF-8");
				response.getWriter().write(objectMapper.writeValueAsString(Result.error("401", "用户名或密码错误")));
			} else if (ex instanceof InternalAuthenticationServiceException) {
				response.setContentType("application/json;charset=UTF-8");
				response.getWriter().write(objectMapper.writeValueAsString(Result.error("401", "没有账号信息")));
			} else if (ex instanceof DisabledException) {
				response.setContentType("application/json;charset=UTF-8");
				response.getWriter().write(objectMapper.writeValueAsString(Result.error("401", "账户被禁用")));
			} else {
				response.setContentType("application/json;charset=UTF-8");
				response.getWriter().write(objectMapper.writeValueAsString(Result.error("401", "登录失败!")));
			}
		} catch (Exception e) {

		}
	}

}

Create a verification code authenticator

Class file description: supports() method: supports custom Token type (VerificationCodeAuthenticationToken)

                    authenticate() method: core method of authenticator

package com.digipower.sercurity.provider;

import java.io.Serializable;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.digipower.entity.SysVerificationCode;
import com.digipower.sercurity.token.VerificationCodeAuthenticationToken;
import com.digipower.sercurity.userservice.UserDetailServiceImpl;
import com.digipower.service.SysVerificationCodeService;

@Component
public class VerificationCodeProvider implements AuthenticationProvider{
	@Autowired
	private UserDetailServiceImpl userDetailServiceImpl;
	@Autowired
	private SysVerificationCodeService sysVerificationCodeService;

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		// TODO Auto-generated method stub
		VerificationCodeAuthenticationToken token = (VerificationCodeAuthenticationToken) authentication;
		String username = (String) token.getPrincipal();// 返回用户名;
		String password = (String) token.getCredentials();// 返回密码
		String code = token.getCode(); // 返回验证码
		
		// 验证码校验
		this.checkCode(code);
		// 用户验证
		UserDetails userDetails = userDetailServiceImpl.loadUserByUsername(username);
		// 密码验证
		this.checkPassword(userDetails, password);
		
		 // 此时鉴权成功后,应当重新 new 一个拥有鉴权的 authenticationResult 返回
		VerificationCodeAuthenticationToken authenticationResult = new VerificationCodeAuthenticationToken(userDetails.getAuthorities(), username, password, code);
		authenticationResult.setDetails(token.getDetails());
		return authenticationResult;
	}

	@Override
	public boolean supports(Class<?> authentication) {
		// TODO Auto-generated method stub
		return (VerificationCodeAuthenticationToken.class.isAssignableFrom(authentication));
	}
	
	private void checkCode(String code){
		QueryWrapper<SysVerificationCode> queryWrapper = new QueryWrapper<SysVerificationCode>();
		queryWrapper.eq("code", code);
		SysVerificationCode object = sysVerificationCodeService.getOne(queryWrapper);
		if(object == null){
			 throw new BadCredentialsException("验证码错误");
		}
		// 验证码验证成功,数据库移除对应指定验证码记录
		sysVerificationCodeService.remove(queryWrapper);
	}
	
	private void checkPassword(UserDetails userDetails, String password){
		if(!String.valueOf(userDetails.getPassword()).equalsIgnoreCase(password)){
			throw new BadCredentialsException("密码错误");
		}
	}

}

Create verification code credentials

package com.digipower.sercurity.token;

import java.util.Collection;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

@SuppressWarnings("serial")
public class VerificationCodeAuthenticationToken extends AbstractAuthenticationToken {
	
	private final Object principal;
	private Object credentials;
	private String code;
	
	public VerificationCodeAuthenticationToken(Object principal,
			Object credentials, String code) {
		super(null);
		this.principal = principal;
		this.credentials = credentials;
		this.code = code;
		setAuthenticated(false);
	}
	
	

	public VerificationCodeAuthenticationToken(Collection<? extends GrantedAuthority> authorities, Object principal,
			Object credentials, String code) {
		super(authorities);
		this.principal = principal;
		this.credentials = credentials;
		this.code = code;
		super.setAuthenticated(true); 
	}



	@Override
	public Object getCredentials() {
		// TODO Auto-generated method stub
		return this.credentials;
	}

	@Override
	public Object getPrincipal() {
		// TODO Auto-generated method stub
		return this.principal;
	}


	public String getCode() {
		return this.code;
	}
	
	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");
        }
 
        super.setAuthenticated(false);
    }



	@Override
	public void eraseCredentials() {
		// TODO Auto-generated method stub
		super.eraseCredentials();
		credentials = null;
	}
}

WebSecurityConfig configuration class modification

package com.digipower.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import com.digipower.sercurity.filter.JWTLoginFilter;
import com.digipower.sercurity.filter.JWTValidFilter;
import com.digipower.sercurity.filter.VerificationCodeLoginFilter;
import com.digipower.sercurity.provider.VerificationCodeProvider;


@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	@Qualifier("userDetailServiceImpl")
	private UserDetailsService userDetailService;
	
	
	
	/**
	 * 自定义Provider
	 */
	@Autowired
	private VerificationCodeProvider verificationCodeProvider;
	

	
	 /**
     * 认证
     *
     * @return
     */
    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        //对默认的UserDetailsService进行覆盖
        authenticationProvider.setUserDetailsService(userDetailService);
        authenticationProvider.setPasswordEncoder(new PasswordEncoder() {

            // 对密码未加密
            @Override
            public String encode(CharSequence rawPassword) {
                return rawPassword.toString();
            }

            // 判断密码是否正确, rawPassword 用户输入的密码,  encodedPassword 数据库DB的密码,当 userDetailService的loadUserByUsername方法执行完后执行
            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
            	return rawPassword.toString().equalsIgnoreCase(encodedPassword);
            }
        });
        return authenticationProvider;
    }
    

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		// TODO Auto-generated method stub
		auth.authenticationProvider(authenticationProvider());
		auth.authenticationProvider(verificationCodeProvider);
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		 
		
		http.addFilter(new JWTValidFilter(authenticationManager()));
        http.addFilter(new JWTLoginFilter(authenticationManager())).csrf().disable();
        
        VerificationCodeLoginFilter verificationCodeLoginFilter = new VerificationCodeLoginFilter("/auth/code",authenticationManager());
        http.addFilterAfter(verificationCodeLoginFilter, UsernamePasswordAuthenticationFilter.class);

		http.sessionManagement()
			.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
			.and()
			.authorizeRequests()
			// 系统默认登入入口
			.antMatchers("/auth/login").permitAll()
			.antMatchers("/auth/code").permitAll()
			// swagger 2不需要鉴权
			.antMatchers("/swagger-ui.html/**").permitAll()
			.antMatchers("/swagger-resources/**").permitAll()
			.antMatchers("/webjars/**").permitAll()
			.antMatchers("/v2/api-docs/**").permitAll()
			// 验证码不需要鉴权
			.antMatchers("/defaultKaptcha").permitAll()
			.anyRequest().authenticated(); // 任何请求,登录后可以访问
			
		 // 开启跨域访问
        http.cors().disable();
        // 开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误
        http.csrf().disable();
        
        
        
        
	}

	@Bean
	public CorsFilter corsFilter() {
		UrlBasedCorsConfigurationSource configurationSource = new UrlBasedCorsConfigurationSource();
		CorsConfiguration cors = new CorsConfiguration();
		cors.setAllowCredentials(true);
		cors.addAllowedOrigin("*");
		cors.addAllowedHeader("*");
		cors.addAllowedMethod("*");
		configurationSource.registerCorsConfiguration("/**", cors);
		return new CorsFilter(configurationSource);
	}
}

Effect demonstration: User name + password + verification code mode

Effect demonstration: user name + password mode

Derivation: Based on this article, the development of springsecurity email verification and springsecurity mobile phone number verification functions can be completed.

github address: https://github.com/zhouzhiwengang/baoan-house

 

Guess you like

Origin blog.csdn.net/zhouzhiwengang/article/details/112586784