一、什么是jwt
JWT
全称是JSON Web Token
,如果从字面上理解感觉是基于JSON
格式用于网络传输的令牌。实际上,JWT
是一种紧凑的Claims
声明格式,旨在用于空间受限的环境进行传输,常见的场景如HTTP
授权请求头参数和URI
查询参数。JWT
会把Claims
转换成JSON
格式,而这个JSON
内容将会应用为JWS
结构的有效载荷或者应用为JWE
结构的(加密处理后的)原始字符串,通过消息认证码(Message Authentication Code
或者简称MAC
)和/或者加密操作对Claims
进行数字签名或者完整性保护。
二、创建后端项目
2.1 添加pom依赖
<!-- hutool 依赖 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
2.2 编辑entity、mapper、service、controller
这里就不过多写了,依旧是跟上一边的文章一样
2.3 JWT 过滤器类
package com.aaa.filter;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import com.aaa.until.ResponseMsg;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class JWTFilter extends OncePerRequestFilter {
/**
* 重写的doFilterInternal方法
* 解析token并验证用户信息
* 如果验证成功,则保存用户信息并放行
* 如果验证失败,则返回错误信息
* 如果token为空且请求路径不在白名单中,则返回错误信息
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 白名单路径
String[] whitename = {"/login"};
// 获取请求头中的token
String token = request.getHeader("token");
// 如果token不为空
if (StringUtils.isNotBlank(token)) {
// 验证token是否有效
boolean verify = JWTUtil.verify(token, "user".getBytes());
// 如果验证通过
if (verify) {
// 解析token,获取用户名和资源信息
JWT jwt = JWTUtil.parseToken(token);
String username = (String) jwt.getPayload("username");
List<String> resources = (List<String>) jwt.getPayload("resources");
// 将资源信息转换为SimpleGrantedAuthority列表
List<SimpleGrantedAuthority> resourceList = resources.stream().map(res -> new SimpleGrantedAuthority(res)).collect(Collectors.toList());
// 保存用户信息
UsernamePasswordAuthenticationToken userPwdToken = new UsernamePasswordAuthenticationToken(username, null, resourceList);
SecurityContextHolder.getContext().setAuthentication(userPwdToken);
// 放行
filterChain.doFilter(request, response);
} else {
// 验证失败,返回错误信息
ResponseMsg responseMsg = new ResponseMsg(401, "没有登陆", null);
printJsonData(response, responseMsg);
}
} else {
// 如果token为空,检查请求路径是否在白名单中
String requestURI = request.getRequestURI();
// 如果在白名单中,放行
if (ArrayUtils.contains(whitename, requestURI)) {
filterChain.doFilter(request, response);
} else {
// 如果不在白名单中,返回错误信息
ResponseMsg responseMsg = new ResponseMsg(401, "没有登陆", null);
printJsonData(response, responseMsg);
}
}
}
public void printJsonData(HttpServletResponse response, ResponseMsg responseMsg) {
try {
response.setContentType("application/json;charset=utf8"); // 设置响应内容类型为JSON,并指定编码为UTF-8
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(responseMsg); // 使用ObjectMapper将ResponseMsg对象转化为JSON字符串
PrintWriter writer = response.getWriter();
writer.print(json); // 将JSON字符串写入响应输出流
writer.flush();//刷新
writer.close();//关闭
} catch (Exception e) {
e.printStackTrace(); // 打印异常堆栈信息
}
}
}
2.4 跨域配置CrossConfig
package com.aaa.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CrossConfig {
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration corsConfiguration = new CorsConfiguration();
//corsConfiguration.setAllowCredentials(true);允许携带cookie
corsConfiguration.addAllowedHeader("*"); // 允许所有的头
corsConfiguration.addAllowedOrigin("*"); // 允许所有的请求源
corsConfiguration.addAllowedMethod("*"); // 所欲的方法 get post delete put
source.registerCorsConfiguration("/**", corsConfiguration); // 所有的路径都允许跨域
return new CorsFilter(source);
}
}
2.5 SecurityConfig 配置类
package com.aaa.config;
import cn.hutool.jwt.JWTUtil;
import com.aaa.filter.JWTFilter;
import com.aaa.until.ResponseMsg;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
protected UserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Resource
public JWTFilter jwtFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
//.usernameParameter("username")//登陆账号
//.passwordParameter("userpassword");//登陆密码
//.defaultSuccessUrl("/test");//登陆成功的跳转路径
// 配置登录页面和登录处理路径
http.formLogin().loginPage("/login.html")//路径前面必须加 /
.loginProcessingUrl("/login")//跟提交的路径一样
// 登录成功的处理逻辑
.successHandler((request, response, authentication) ->{
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();//获取资源信息
List<String> resources = authorities.stream().map(auth -> auth.getAuthority()).collect(Collectors.toList());//转为stream流变成list
Map map = new HashMap<>();
map.put("username",authentication.getName());
map.put("resources",resources);
//生成token
String token = JWTUtil.createToken(map, "user".getBytes());
ResponseMsg responseMsg = new ResponseMsg(200,"登陆成功",token);
//响应返回信息
printJsonData(response,responseMsg);
})
// 登录失败的处理逻辑
.failureHandler((request, response, exception)->{
ResponseMsg responseMsg = new ResponseMsg(400,"验证失败");
printJsonData(response,responseMsg);
});
// 配置权限不允许的处理逻辑
http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
ResponseMsg responseMsg = new ResponseMsg(403,"权限不允许",null);
printJsonData(response,responseMsg);
});
// 配置不需要验证的路径
http.authorizeRequests().antMatchers("/login.html", "login").permitAll();//不用验证
//http.authorizeRequests().antMatchers("/test").hasRole("test");//必须有哪个权限才能访问
//http.authorizeRequests().antMatchers("/test").hasAnyAuthority("resource");//必须有哪个资源才能访问
// 配置其他路径需要验证
http.authorizeRequests().anyRequest().authenticated();
// 添加JWT过滤器
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
//关闭csrf保护
http.csrf().disable();
// 配置跨域
http.cors();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
public void printJsonData(HttpServletResponse response, ResponseMsg responseMsg) {
try {
response.setContentType("application/json;charset=utf8"); // json格式 编码是中文
ObjectMapper objectMapper = new ObjectMapper();
String s = objectMapper.writeValueAsString(responseMsg);// 使用ObjectMapper将result转化json为字符串
PrintWriter writer = response.getWriter();
writer.print(s);
writer.flush();
writer.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
2.6 SecurityService类
也是跟上一篇的笔记一样
三、创建前端vue项目
login页面
不要忘了在路由下添加login页面
<template>
<div>
<el-form style="width: 400px ;margin: auto auto" :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="名字" prop="username">
<el-input type="text" v-model="ruleForm.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="ruleForm.password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">提交</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import qs from 'qs';
export default {
data() {
return {
ruleForm: {
username: 'user000',//每次刷新页面都要重新输入账号密码,所以直接把账号密码写入进来了
password: '123456',
},
rules: {
username: [
{required: true, message: '请输入账户', trigger: 'blur'}
],
password: [
{required: true, message: '请输入密码', trigger: 'blur'}
]
}
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
alert('验证成功,准备提交')
this.$axios.post('/login',qs.stringify(this.ruleForm)).then(res=>{
console.log(res)
if (res.data.code == 200){
console.log(res.data.data);
//--------------------------------------------------------------
sessionStorage.setItem("token",res.data.data)在sessionstorage中添加一个为token的数据
//--------------------------------------------------------------
location.href='/main';//成功访问main页面
}else {
}
})
//location.href='/main'
} else {
alert('验证失败');
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
}
</script>
在main.js添加下面的代码
验证token是否存在,如果有的话就在请求头里面添加token
// 请求拦截器
instance.interceptors.request.use(config => {
if (sessionStorage.getItem("token")){
let token = sessionStorage.getItem("token");
config.headers['token'] = token;
}
return config;
}, error => {
return Promise.reject(error);
});
四、总结
从前端页面输入账号向后端发送跨域请求,后端接受参数验证是否在数据库中存在,如果存在就去把用户在数据库中的角色权限和资源权限查出来,保存在Security中.登陆成功后就把用户的信息用JWTUtil.createToken()方法生成token,返回给前端,前端拿到token,前端再次向后端发起请求时携带token,后端识别,如果正确的话就放行,不正确就访问失败