SpringBoot+MyBatis-plus+SpringSecurity+JWT login authentication to achieve separation of front and back ends

SpringBoot+MyBatis-plus+SpringSecurity+JWT login authentication to achieve separation of front and back ends  

1. Spring Security brief

  • Certification (who are you)
  • Authorization (what can you do)

  • The core of attack protection (preventing identity forgery) is a set of filter chains, which will be automatically configured after the project is launched. The core is the Basic Authentication Filter used to authenticate the user's identity, a filter in spring security handles an authentication method.

2. JWT brief

  • JSON Web Token (JWT) is an open standard (RFC 7519), which defines a compact, self-contained way to securely transmit information between parties as JSON objects. This information can be verified and trusted because it is digitally signed.

  • The JSON Web Token consists of three parts. The
    first part is connected by a dot (.). We call it the header. The second part is called the payload (payload, similar to items carried on an airplane). The third part is the visa (signature).

3. The code execution flow chart of this article is roughly as follows

4. Core function code

pom.xml dependency configuration:

        <!-- 集成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 core code

JwtUserDetails

Class description: inherit UserDetails permission judgment attribute (Security framework)

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

Class description: Inherit UserDetailsService, login authentication method, configure and specify this class for authentication by 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 configuration class

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 core code

JWTLoginFilter

Class description: user-defined login interceptor, user login method, login success method and login failure method

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

Class description: After the user logs in successfully, carry the token to access other data interface credentials to determine the interceptor

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

Class description: jwt tools

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 project construction

Please refer to the article address: SpringBoot + MyBatis-plus + Druid to realize simple addition, deletion, checking, dynamic condition query and paging function

8. Postman requests a demo 

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

Guess you like

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