SpringBoot+MyBatis-plus+SpringSecurity+JWT 验证码认证解析

访客 阅读:223 2021-03-31 12:47:17 评论:0

本文基于SpringBoot+MyBatis-plus+SpringSecurity+JWT 登入认证,实现前后端分离基础之上追加验证码登入认证功能。

1、springsecurity 验证码模式核心代码

创建验证码过滤器

在filter中增加VerificationCodeLoginFilter。这里过滤的是”/auth/code”的请求,用不同的请求地址来区分不同的登录认证方式。

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) { 
 
		} 
	} 
 
} 

创建验证码认证器

类文件说明: supports() 方法:支持自定义Token 类型(VerificationCodeAuthenticationToken)

                    authenticate()方法: 认证器核心方法

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("密码错误"); 
		} 
	} 
 
} 

创建验证码凭证

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配置类修改

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

效果演示: 用户名+ 密码+ 验证码模式

效果演示: 用户名+ 密码模式

推导:基于本文可以完成基于springsecurity邮箱验证和springsecurity手机号码验证功能开发。

github 地址:https://github.com/zhouzhiwengang/baoan-house

 

声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

发表评论
搜索
排行榜
KIKK导航

KIKK导航

关注我们