SpringBoot+MyBatis-plus+SpringSecurity+JWT 登入认证,实现前后端分离解析

访客 阅读:230 2021-03-31 12:52:00 评论:0

SpringBoot+MyBatis-plus+SpringSecurity+JWT 登入认证,实现前后端分离  

1、SpringSecurity简要

  • 认证 (你是谁)
  • 授权 (你能干什么)
  • 攻击防护 (防止伪造身份)
    其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。

2、JWT 简要

  • JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

  • JSON Web Token由三部分组成,它们之间用圆点(.)连接
    第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

3、本文代码执行流程图大致如下

4、核心功能代码

pom.xml 依赖配置:

        <!-- 集成springsecurity 安全框架  --> 
		<dependency> 
			<groupId>org.springframework.boot</groupId> 
			<artifactId>spring-boot-starter-security</artifactId> 
		</dependency> 
		<!-- 集成jwt 框架  --> 
		<dependency> 
			<groupId>io.jsonwebtoken</groupId> 
			<artifactId>jjwt</artifactId> 
			<version>0.9.0</version> 
		</dependency>

5、springsecurity 核心代码

JwtUserDetails

类说明:继承 UserDetails 权限判断属性( Security框架)

package com.digipower.sercurity.entity; 
 
import java.util.Collection; 
 
import org.springframework.security.core.GrantedAuthority; 
import org.springframework.security.core.userdetails.UserDetails; 
 
@SuppressWarnings("serial") 
public class JwtUserDetails implements UserDetails { 
	private String username; 
	 
	private String userPin; 
 
    private String password; 
     
    private String sid; 
 
    private Collection<? extends GrantedAuthority> authorities; 
     
    public String getUserPin() { 
		return userPin; 
	} 
 
	public void setUserPin(String userPin) { 
		this.userPin = userPin; 
	} 
 
	public String getSid() { 
		return sid; 
	} 
 
	public void setSid(String sid) { 
		this.sid = sid; 
	} 
	 
	 
 
	public JwtUserDetails() { 
		super(); 
		// TODO Auto-generated constructor stub 
	} 
 
	public JwtUserDetails(String username, String password, String userPin, Collection<? extends GrantedAuthority> authorities) { 
        this.username = username; 
        this.password = password; 
        this.userPin = userPin; 
        this.authorities = authorities; 
    } 
 
    @Override 
    public Collection<? extends GrantedAuthority> getAuthorities() { 
        return authorities; 
    } 
 
    @Override 
    public String getPassword() { 
        return password; 
    } 
 
    @Override 
    public String getUsername() { 
        return username; 
    } 
 
    // 账户是否未过期 
    @Override 
    public boolean isAccountNonExpired() { 
        return true; 
    } 
 
    // 账户是否未被锁 
    @Override 
    public boolean isAccountNonLocked() { 
        return true; 
    } 
 
    @Override 
    public boolean isCredentialsNonExpired() { 
        return true; 
    } 
 
    @Override 
    public boolean isEnabled() { 
        return true; 
    } 
} 

UserDetailServiceImpl

类说明:继承 UserDetailsService ,登录认证方法,由SecurityConfig 来配置指定此类来认证

package com.digipower.sercurity.userservice; 
 
import java.util.ArrayList; 
import java.util.List; 
 
import org.apache.commons.collections.CollectionUtils; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.security.core.GrantedAuthority; 
import org.springframework.security.core.authority.SimpleGrantedAuthority; 
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.Component; 
 
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 
import com.digipower.entity.UcasAuthUserInfo; 
import com.digipower.sercurity.entity.JwtUserDetails; 
import com.digipower.service.UcasAuthUserInfoService; 
 
@Component("userDetailServiceImpl") 
public class UserDetailServiceImpl implements UserDetailsService { 
	@Autowired 
	private UcasAuthUserInfoService ucasAuthUserInfoService; 
 
	@Override 
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 
		// TODO Auto-generated method stub 
		// 根据用户名去查找用户信息 
		QueryWrapper<UcasAuthUserInfo> queryWrapper = new QueryWrapper<UcasAuthUserInfo>(); 
		queryWrapper.eq("user_pin", username); 
		List<UcasAuthUserInfo> list = ucasAuthUserInfoService.list(queryWrapper); 
 
		if (CollectionUtils.isEmpty(list)) { 
			throw new UsernameNotFoundException(String.format("Not user Found with '%s'", username)); 
		} 
		UcasAuthUserInfo customer = list.get(0); 
		return new JwtUserDetails(customer.getUserName(), customer.getPassword(), customer.getUserPin(), getGrantedAuthority()); 
     
	} 
 
	/** 
	 * 用户登入成功默认所有角色权限 
	 * @return 
	 */ 
	private List<GrantedAuthority> getGrantedAuthority() { 
		List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); 
		authorities.add(new SimpleGrantedAuthority("all")); 
		return authorities; 
	} 
 
} 

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.AuthenticationProvider; 
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 
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.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; 
 
 
@Configuration 
@EnableWebSecurity 
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 
	@Autowired 
	@Qualifier("userDetailServiceImpl") 
	private UserDetailsService userDetailService; 
	 
	 /** 
     * 认证 
     * 
     * @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(HttpSecurity http) throws Exception { 
		  
		 
		http.addFilter(new JWTValidFilter(authenticationManager())); 
        http.addFilter(new JWTLoginFilter(authenticationManager())).csrf().disable(); 
 
		http.sessionManagement() 
			.sessionCreationPolicy(SessionCreationPolicy.STATELESS) 
			.and() 
			.authorizeRequests() 
			.antMatchers("/auth/login").permitAll() 
			.antMatchers("/swagger-ui.html/**").permitAll() 
			.antMatchers("/swagger-resources/**").permitAll() 
			.antMatchers("/webjars/**").permitAll() 
			.antMatchers("/v2/api-docs/**").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); 
	} 
} 

6、JWT 核心代码

JWTLoginFilter

类说明:用户自定义登入拦截器、处理用户登入方法、登入成功方法和登入失败方法

package com.digipower.sercurity.filter; 
 
import java.io.IOException; 
import java.io.PrintWriter; 
import java.util.ArrayList; 
import java.util.Map; 
 
import javax.servlet.FilterChain; 
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.BadCredentialsException; 
import org.springframework.security.authentication.DisabledException; 
import org.springframework.security.authentication.InternalAuthenticationServiceException; 
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 
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.UsernamePasswordAuthenticationFilter; 
 
import com.digipower.common.entity.Result; 
import com.digipower.sercurity.entity.JwtUserDetails; 
import com.digipower.sercurity.util.JwtTokenUtil; 
import com.fasterxml.jackson.databind.ObjectMapper; 
 
/*** 
 * TODO  登录  ===> POST请求( 账号:username=?, 密码:password=?) 
 * 
 * 登录会调用springSecurity的登录方法进行验证 
 *<p> 
 * ===== 登录成功 
 * http状态status状态返回200,并且自定义响应状态code返回200,响应头存放token,key = token,value = jwt生成的token内容 
 * ===== 登录失败 
 * http状态status状态返回401,并且自定义响应状态code返回401,并提示对应的内容 
 * ===== 权限不足 
 *  http状态status状态返回403,并且自定义响应状态code返回403,并提示对应的内容 
 * </p> 
 * @author zzg 
 */ 
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter { 
	 /** 
     * 获取授权管理, 创建JWTLoginFilter时获取 
     */ 
    private AuthenticationManager authenticationManager; 
 
   
    /** 
     * 创建JWTLoginFilter,构造器,定义后端登陆接口-【/auth/login】,当调用该接口直接执行 attemptAuthentication 方法 
     * 
     * @param authenticationManager 
     */ 
    public JWTLoginFilter(AuthenticationManager authenticationManager) { 
        this.authenticationManager = authenticationManager; 
        super.setFilterProcessesUrl("/auth/login"); 
    } 
 
 
    /** 
     * TODO 一旦调用登录接口 /auth/login,立即执行该方法 
     * 
     * @param request 
     * @param response 
     * @return 
     * @throws AuthenticationException 
     */ 
    @Override 
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) { 
    	JwtUserDetails user = null; 
    	ObjectMapper objectMapper = new ObjectMapper(); 
        try { 
            user = new ObjectMapper().readValue(request.getInputStream(), JwtUserDetails.class); 
        } catch (IOException e) { 
        	try{ 
	            response.setContentType("application/json;charset=UTF-8"); 
	  	      	response.getWriter().write(objectMapper.writeValueAsString(Result.error("401","没有传递对应的参数"))); 
        	} catch(Exception message){ 
        		 
        	} 
            return null; 
        } 
        // 调用springSecurity的 XiJiaUserDetailsServiceImpl 的 loadUserByUsername 方法进行登录认证,传递账号密码 
        return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>())); 
    } 
 
    /** 
     * 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,并返回 
    	JwtUserDetails userEntity = (JwtUserDetails) authResult.getPrincipal(); 
        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(); 
    } 
 
 
    /** 
     * TODO 一旦调用 springSecurity认证失败 ,立即执行该方法 
     * 
     * @param request 
     * @param response 
     * @param ex 
     * @throws IOException 
     * @throws ServletException 
     */ 
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) { 
    	 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){ 
    		 
    	} 
    } 
} 

JWTValidFilter

类说明:用户登入成功后,携带token 访问其他数据接口凭证判断拦截器

package com.digipower.sercurity.filter; 
 
import java.io.IOException; 
import java.security.SignatureException; 
import java.util.ArrayList; 
import java.util.List; 
 
import javax.servlet.FilterChain; 
import javax.servlet.ServletException; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
 
import org.apache.commons.lang3.StringUtils; 
import org.springframework.security.authentication.AuthenticationManager; 
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 
import org.springframework.security.core.authority.SimpleGrantedAuthority; 
import org.springframework.security.core.context.SecurityContextHolder; 
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 
 
import com.digipower.common.entity.Result; 
import com.digipower.sercurity.util.JwtTokenUtil; 
import com.fasterxml.jackson.databind.ObjectMapper; 
 
import io.jsonwebtoken.ExpiredJwtException; 
 
public class JWTValidFilter extends BasicAuthenticationFilter { 
	/** 
     * SecurityConfig 配置中创建该类实例 
     */ 
    public JWTValidFilter(AuthenticationManager authenticationManager) { 
        // 获取授权管理 
        super(authenticationManager); 
    } 
 
 
    /** 
     * 拦截请求 
     * 
     * @param request 
     * @param response 
     * @param chain 
     * @throws IOException 
     * @throws ServletException 
     */ 
    @Override 
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { 
    	ObjectMapper objectMapper = new ObjectMapper(); 
    	// 获取token, 没有token直接放行 
        String token = request.getHeader("token"); 
        if (StringUtils.isBlank(token) || "null".equals(token)) { 
            super.doFilterInternal(request, response, chain); 
            return; 
        } 
        // 有token进行权限验证 
        String username = null; 
        try { 
            //  获取账号 
            username = JwtTokenUtil.getUsername(token); 
        } catch (ExpiredJwtException ex) { 
        	response.setContentType("application/json;charset=UTF-8"); 
  	      	response.getWriter().write(objectMapper.writeValueAsString(Result.error("10000","登录过期"))); 
            return; 
        } catch (Exception e) { 
        	response.setContentType("application/json;charset=UTF-8"); 
  	      	response.getWriter().write(objectMapper.writeValueAsString(Result.error("10000","JWT解析错误"))); 
            return; 
        } 
        //  添加账户的权限信息,和账号是否为空,然后保存到Security的Authentication授权管理器中 
        if (StringUtils.isNotBlank(username)) { 
            SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(username, null, new ArrayList<SimpleGrantedAuthority>())); 
        } 
        super.doFilterInternal(request, response, chain); 
    } 
} 

JwtTokenUtil

类说明:jwt 工具类

package com.digipower.sercurity.util; 
 
import java.util.Date; 
import org.springframework.stereotype.Component; 
import com.digipower.sercurity.entity.JwtUserDetails; 
import io.jsonwebtoken.Claims; 
import io.jsonwebtoken.Jwts; 
import io.jsonwebtoken.SignatureAlgorithm; 
import lombok.Data; 
 
/** 
 * jwt 工具类 
 *  
 * @author zzg 
 * 
 */ 
public class JwtTokenUtil { 
	 // 主题 
    private static final String SUBJECT = "digipower"; 
 
    // jwt的token有效期, 
    //private static final long EXPIRITION = 1000L * 60 * 60 * 24 * 7;//7天 
    private static final long EXPIRITION = 1000L * 60 * 30;   // 半小时 
 
    // 加密key(黑客没有该值无法篡改token内容) 
    private static final String APPSECRET_KEY = "digipower"; 
 
    // 用户url权限列表key 
    private static final String AUTH_CLAIMS = "auth"; 
 
    /** 
     * TODO  生成token 
     * 
     * @param user 
     * @return java.lang.String 
     * @date 2020/7/6 0006 9:26 
     */ 
    public static String generateToken(JwtUserDetails user) { 
        String token = Jwts 
                .builder() 
                // 主题 
                .setSubject(SUBJECT) 
                // 添加jwt自定义值 
                .claim(AUTH_CLAIMS, user.getAuthorities()) 
                .claim("username", user.getUsername()) 
                .claim("userPin", user.getUserPin()) 
                .claim("sid", user.getSid()) 
                .setIssuedAt(new Date()) 
                // 过期时间 
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRITION)) 
                // 加密方式,加密key 
                .signWith(SignatureAlgorithm.HS256, APPSECRET_KEY).compact(); 
        return token; 
    } 
 
 
    /** 
     * 获取用户Id 
     * 
     * @param token 
     * @return 
     */ 
    public static String getUserId(String token) { 
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody(); 
        return claims.get("sid").toString(); 
    } 
 
 
    /** 
     * 获取用户名 
     * 
     * @param token 
     * @return 
     */ 
    public static String getUsername(String token) { 
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody(); 
        return claims.get("username").toString(); 
    } 
 
    
 
    /** 
     * 是否过期 
     * 
     * @param token 
     * @return 
     */ 
    public static boolean isExpiration(String token) { 
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody(); 
        System.out.println("过期时间: " + claims.getExpiration()); 
        return claims.getExpiration().before(new Date()); 
    } 
} 

7、SpringBoot + MyBatis-plus + Druid + MySQL8 项目搭建

请参考文章地址:SpringBoot + MyBatis-plus + Druid 实现简单增删查改、动态条件查询和分页功能

8、Postman 请求演示 

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

声明

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

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

KIKK导航

关注我们