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

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

猜你喜欢

转载自blog.csdn.net/zhouzhiwengang/article/details/112556678