背景:本着理论兼动手能力,自己搭建了目前主流的spring boot项目和vue项目。先学习java开发相关的某一块理论知识,然后在项目中实现。同时也完成前端的项目,全面提升自我!后续会将学习过程记录再次。
springboot: 2.3.0.BUILD-SNAPSHOT
vue: 2.6.12
axios: 0.19.2
本次内容主要记录在前后端分离项目中,如何实现security + JWT 实现token认证登录
相关依赖导入:
org.springframework.boot
spring-boot-starter-security
io.jsonwebtoken
jjwt
0.9.1
创建user表
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
`age` int(3) DEFAULT NULL,
`flag` int(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除状态 0-未删除 1-删除',
`password` varchar(16) DEFAULT '' COMMENT '用户登录密码',
`username` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户的账号',
`role` varchar(255) NOT NULL COMMENT '用户角色',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;
创建UserInfo实体类
package com.example.network.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @program com.example.demo.domain
* @description user
* @auther Mr.Xiong
* @create 2020-03-07 12:53
*/
@Getter
@Setter
@ToString
public class UserInfo {
private Integer id;
private String username;
private Integer age;
private Integer flag;
private String password;
private String role;
public UserInfo() {
}
public UserInfo(String username, String password, String role) {
this.username = username;
this.password = password;
this.role = role;
}
}
security相关的配置,处理跨域、密码加密方式等
package com.example.network.framework.security;
import com.example.network.framework.jwt.JWTAuthenticationFilter;
import com.example.network.framework.jwt.JWTAuthorizationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
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.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @program com.example.network.framework.security
* @description security登录认证
* @auther Mr.Xiong
* @create 2021-05-13 21:37:43
*/
@Configuration
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService userDetailsService;
//定义登陆成功返回信息
private class AjaxAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("用户[" + SecurityContextHolder.getContext().getAuthentication().getPrincipal() +"]登陆成功!");
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write("{\"status\":\"ok\",\"msg\":\"登录成功\"}");
out.flush();
out.close();
}
}
//定义登陆失败返回信息
private class AjaxAuthFailHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
System.out.println("用户登陆失败 AjaxAuthFailHandler");
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
PrintWriter out = response.getWriter();
out.write("{\"status\":\"error\",\"msg\":\"请检查用户名、密码或验证码是否正确\"}");
out.flush();
out.close();
}
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable() //关闭csrf
.authorizeRequests()
.antMatchers("/login/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); //禁用session
//登录配置
http.formLogin()
.successHandler(new AjaxAuthSuccessHandler())
.failureHandler(new AjaxAuthFailHandler())
//设置自己的登录界面(如果不设置,将会是自带的登录界面这里我们使用自定义的登录界面)
.loginPage("/login/login")
//表单提交的url
.loginProcessingUrl("/login/login");
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
创建MyUserDetailsService实现UserDetailsService接口,重写 loadUserByUsername方法
package com.example.network.framework.security;
import com.example.network.domain.UserInfo;
import com.example.network.framework.jwt.JwtUser;
import com.example.network.service.UserService;
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 java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* @program com.example.network.framework.security
* @description 系统用户登录验证对象
* @auther Mr.Xiong
* @create 2021-05-13 21:45:39
*/
@Component
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
UserInfo user = userService.queryUserByAccount(userName);
if (Objects.isNull(user)) {
throw new UsernameNotFoundException("用戶不存在");
}
List authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_" + user.getRole()));
return new JwtUser(user);
}
}
JWT相关配置,创建JwtUser类,实现UserDetails接口
package com.example.network.framework.jwt;
import com.example.network.domain.UserInfo;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.Collection;
import java.util.Collections;
/**
* @program com.example.network.framework.jwt
* @description jwtUserInfo
* @auther Mr.Xiong
* @create 2021-08-14 13:17:07
*/
@Getter
@Setter
public class JwtUser implements UserDetails {
private Integer id;
private String username;
private String password;
private Collection extends GrantedAuthority> authorities;
public JwtUser(UserInfo user) {
id = user.getId();
username = user.getUsername();
password = new BCryptPasswordEncoder().encode(user.getPassword());
//这里说明一下,必须要加上ROLE_开头,或者在数据库直接以这个开头
authorities = Collections.singleton(new SimpleGrantedAuthority("ROLE_"+user.getRole()));
}
// 获取权限信息
@Override
public Collection extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
// 账号是否未过期,默认是false,记得要改一下
@Override
public boolean isAccountNonExpired() {
return true;
}
// 账号是否未锁定,默认是false,记得也要改一下
@Override
public boolean isAccountNonLocked() {
return true;
}
// 账号凭证是否未过期,默认是false,记得还要改一下
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
创建JwtTokenUtils,jwt的token工具类
package com.example.network.framework.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
/**
* @program com.example.network.framework.jwt
* @description jwtToken 工具类
* @auther Mr.Xiong
* @create 2021-08-14 13:26:42
*/
public class JwtTokenUtils {
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
private static final String SECRET = "jwtsecretdemo";
private static final String ISS = "echisan";
// 角色的key
private static final String ROLE_CLAIMS = "rol";
// 过期时间是3600秒,既是1个小时
private static final long EXPIRATION = 3600L;
// 创建token
public static String createToken(String username,String role) {
long expiration =EXPIRATION;
HashMap map = new HashMap<>();
map.put(ROLE_CLAIMS, role);
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, SECRET)
.setClaims(map)
.setIssuer(ISS)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.compact();
}
// 从token中获取用户名
public static String getUsername(String token){
return getTokenBody(token).getSubject();
}
// 获取用户角色
public static String getUserRole(String token){
return (String) getTokenBody(token).get(ROLE_CLAIMS);
}
// 是否已过期
public static boolean isExpiration(String token) {
try {
return getTokenBody(token).getExpiration().before(new Date());
} catch (ExpiredJwtException e) {
return true;
}
}
private static Claims getTokenBody(String token){
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}
}
创建用户权限拦截器JWTAuthorizationFilter
package com.example.network.framework.jwt;
import com.fasterxml.jackson.databind.ObjectMapper;
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 javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
/**
* @program com.example.network.framework.jwt
* @description 用户权限的拦截器
* @auther Mr.Xiong
* @create 2021-08-14 13:44:03
*/
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
// 如果请求头中没有Authorization信息则直接放行了
if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// 如果请求头中有token,则进行解析,并且设置认证信息
try {
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
} catch (Exception e) {
//返回json形式的错误信息
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
String reason = "统一处理,原因:" + e.getMessage();
response.getWriter().write(new ObjectMapper().writeValueAsString(reason));
response.getWriter().flush();
return;
}
super.doFilterInternal(request, response, chain);
}
// 这里从token中获取用户信息并新建一个token
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws Exception {
String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
boolean expiration = JwtTokenUtils.isExpiration(token);
if (expiration) {
throw new Exception("token超时了");
} else {
String username = JwtTokenUtils.getUsername(token);
String role = JwtTokenUtils.getUserRole(token);
if (username != null) {
return new UsernamePasswordAuthenticationToken(username, null,
Collections.singleton(new SimpleGrantedAuthority(role))
);
}
}
return null;
}
}
创建用户登录拦截器JWTAuthLoginFilter
package com.example.network.framework.jwt;
import com.example.network.domain.UserInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
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.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
/**
* @program com.example.network.framework.jwt
* @description 用户登录信息 拦截器
* @auther Mr.Xiong
* @create 2021-08-14 13:29:28
*/
public class JWTAuthLoginFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthLoginFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
//设置登录请求的url
super.setFilterProcessesUrl("/login/login");
}
@SneakyThrows
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 从输入流中获取到登录的信息
try {
UserInfo loginUser = new ObjectMapper().readValue(request.getInputStream(), UserInfo.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>()));
//密码错误时抛出异常
}catch (BadCredentialsException b){
System.out.println("密码错误");
try {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("密码错误");
} catch (IOException e) {
e.printStackTrace();
}
return null;
//无此用户时抛出异常
}catch (InternalAuthenticationServiceException i){
System.out.println("没有此用户");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("xxx");
return null;
}
catch (Exception e) {
e.printStackTrace();
return null;
}
}
// 成功验证后调用的方法
// 如果验证成功,就生成token并返回
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
System.out.println("登录成功");
JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
System.out.println("jwtUser:" + jwtUser.toString());
String role = "";
Collection extends GrantedAuthority> authorities = jwtUser.getAuthorities();
for (GrantedAuthority authority : authorities) {
role = authority.getAuthority();
}
String token = JwtTokenUtils.createToken(jwtUser.getUsername(), role);
// 返回创建成功的token
// 但是这里创建的token只是单纯的token
// 按照jwt的规定,最后请求的时候应该是 `Bearer token`
System.out.println(token);
response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token);
//这里我还将该用户的id进行返回了
response.setIntHeader("id",jwtUser.getId().intValue());
}
}
前端参数配置: main.js配置
import Api from 'axios'
//给axios请求设置默认的token请求头
const token = window.sessionStorage.getItem("token");
Api.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
Api.defaults.headers['Authorization'] = token;
Api.defaults.headers.post['Content-Type'] = 'text/plain';
请求登录接口:前端代码(登录到后端,结构直接被拦截并进行验证,所有这里就不写controller)
// ruleForm结构:
ruleForm: {
username: '',
password: ''
}
// 请求后端的登录接口
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$api.post('/api/login/login', this.ruleForm).then(response => {
if (response.config.data) {
//登录成功后,将后端给的token以及uid都存在session中
window.sessionStorage.setItem("token", response.headers.token);
window.sessionStorage.setItem("id",response.headers.uid);
console.log("登陆成功");
this.goHome();
} else {
console.log('账号密码不匹配!,登录失败');
}
})
} else {
console.log('账号密码不能为空!,登录失败');
return false;
}
});
}
登录成功后,进入主页人际关系topo图
至此,手动操作结束,理论知识后续会慢慢补上。