1.前言

观看这篇随笔需要有spring security基础。

心得:

1.生成token 的变化数据是用户名和权限拼接的字符串 ,其他的固定
2.生成的token是将登录通过的用户的权限拼接的字符串加密后放入里面后加密,当携带token访问时被拦截后,会将token解析出的权限注册,因为不与数据库等数据共享校验权限最新信息,
如果在携带token的请求前权限有变化,但是token却没有改变,会导致token权限与用户真实权限不一致,形成脏数据啦!!!
如果权限增加还好,使得无法访问新加权限的操作,如果是减少权限,比如vip过期,用户仍然可以有vip权限。
3.解决token脏数据的方案有两个:
(1)等待该token失效时间【不靠谱】;
(2)每次修改权限时,会强制使得token失效,具体怎么做,还没试过
4.当然,也有优点的,不与数据库等最新数据做权限对比操作,较少了访问数据库该用户信息的部分,能快速的过滤请求权限,理论上访问数据会变快。
5.可以设置过期时间,单位毫秒,用时间戳设置 ,到时间则不可在使用,
但是缺点很明显,在未过期之前,可以无数次访问验证通过,无法控制使用次数,
因此不能作为资源服务器对第三方应用开放的授权令牌,
6.令牌格式对不上,会直接报错异常,为了服务降级,做个异常捕获即可
7.如果生成了新的令牌,旧的令牌仍然可以使用,因此会导致多设备同时登录的情况,无法控制登录数量
8.使用jwt[java web token],做登录校验,则会导致http.sessionManagement().maximumSessions(1);设置失效,因为没有使用session做为登录控制
//
安全弊端很多 , 但是让我深刻明白了token的内部思想
完全可以使用redis来完成用户token的管理,但是这样每次都需要向redis查询一次,就让jave web token 不伦不类了 。。。。
虽然已经尽可能的让服务器减少负担和提高反应速度,但仍然感觉好鸡肋
//
当然应用场景还是有的,可用于分享连接的使用,这样既能向有令牌的用户使用被分享的权限,而且不需要去数据库获取数据对其进行对比,降低服务器负担,
也就是说比较适合半开放性的功能使用
//
工程我放在GitHub仓库了
https://github.com/cen-xi/spring-security-JWT

2.操作

看我的源码大招,写了很多注释了,足够详细了,我懒得再写说明

(1)目录结构

(2)添加依赖包

pom.xml源码

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>security-jwt-5605</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>security-jwt-5605</name>
<description>Demo project for Spring Boot</description> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency> <!-- jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency> </dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

(3)做一个实体类,继承  UserDetails

package com.example.securityjwt5605.model;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection;
import java.util.List; public class JwtUser implements UserDetails {
//属性名 username 和 password 是固定死的,不可更改,否则报错
//但 grantedAuthorities 可随意 private String username;
private String password;
private List<GrantedAuthority> grantedAuthorities; @Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return grantedAuthorities;
} @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;
} public void setUsername(String username) {
this.username = username;
} public void setPassword(String password) {
this.password = password;
} public void setGrantedAuthorities(List<GrantedAuthority> grantedAuthorities) {
this.grantedAuthorities = grantedAuthorities;
}
}

(4)用户名密码登录过滤器

package com.example.securityjwt5605.filters;

import com.example.securityjwt5605.model.JwtUser;
import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
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.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; 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.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map; /**
* 首次登录才调用这个方法
*/ public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter {
//构造方法 ,记得使用 public
//第一个是 登录路径 。第二个是 认证管理者
//在启动的时候就已经h已经执行了
public JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {
super(new AntPathRequestMatcher(defaultFilterProcessesUrl));
System.out.println("===============================登录拦截1==================================");
//存储到父类,可不加 super.便于方法 attemptAuthentication()调用,
setAuthenticationManager(authenticationManager); } /**
*访问/login登录后首先进入这里
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse resp) throws AuthenticationException, IOException, ServletException {
JwtUser user = new JwtUser();
System.out.println("===============================登录拦截2==================================");
try {
//从请求中获取用户验证信息
//将json字符串解析
user = new ObjectMapper().readValue(req.getInputStream(), JwtUser.class);
// }catch (Exception ignored){
// //Exception ignored表示忽略异常
// //这样内部可以不写内容
// }
// String username = req.getParameter("username");
// String password = req.getParameter("password");
// if (username == null || password == null){
// throw new Exception();
// }
// user.setUsername(username);
// user.setPassword(password);
}catch (Exception e){
//Exception ignored表示忽略异常
System.out.println("请求无法解析出JwtUser对象"); }
//对请求做认证操作,如何校验,由默认的程序完成,不涉及对比操作,因为用户信息存在内存中,否则需要修改 securityConfig.java 的 configure(AuthenticationManagerBuilder auth) 用于设置数据库操作
//认证管理对象执行认证方法,new 一个用户密码认证令牌对象,参数为用户名和密码,然后放入认证方法中
//然后执行登录验证
return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword())); } //认证成功
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { System.out.println("===============================登录拦截3==================================");
//获取登录角色的权限
//这是权限 ,如果登录内存只有角色配置,无权限配置,则自动添加前缀构成权限 ROLE_角色
Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
//线程安全
StringBuffer stringBuffer = new StringBuffer();
for (GrantedAuthority grantedAuthority : authorities) {
System.out.println("当前有的权限:"+grantedAuthority);
//用逗号隔开好一点,不然后面需要手动切割
stringBuffer.append(grantedAuthority.getAuthority()).append(",");
}
//生成令牌 token
String jwt = Jwts.builder()
//登录角色的权限,这会导致如果权限更改,该token无法及时更新权限信息
.claim("authorities", stringBuffer)
//用户名
.setSubject(authResult.getName())
//存活时间,过期则判为无效
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 10))
//签名,第一个参数时算法,第二个参数时内容,内容可随意写
.signWith(SignatureAlgorithm.HS512, "java521@java")
//协议完成
.compact();
System.out.println(jwt);
System.out.println("======================");
System.out.println(stringBuffer);
//设置json数据返回给前端
Map<String, Object> map = new HashMap<>();
map.put("token", jwt);
map.put("msg", "登录成功");
//MediaType.APPLICATION_JSON_UTF8_VALUE 等用于 "application/json;charset=UTF-8"
// response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setContentType("application/json;charset=utf-8");
PrintWriter printWriter = response.getWriter();
//转成json后传送
printWriter.write(new ObjectMapper().writeValueAsString(map));
//关闭流
printWriter.flush();
printWriter.close(); } //认证失败
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
System.out.println("===============================登录拦截4==================================");
Map<String, Object> map = new HashMap<>();
map.put("msg", "登录失败");
response.setContentType("application/json;charset=utf-8");
PrintWriter printWriter = response.getWriter();
//转成json后传送
printWriter.write(new ObjectMapper().writeValueAsString(map));
//关闭流
printWriter.flush();
printWriter.close();
}
}

(5)token访问过滤器

package com.example.securityjwt5605.filters;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.filter.GenericFilterBean;
import sun.plugin.liveconnect.SecurityContextHelper; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.security.Security;
import java.util.List; /**
* 对携带token的请求做token检查,对比是否正确,正确则可以直接通过
*/ public class JwtFilter extends GenericFilterBean { @Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("===============================token登录拦截1=================================="); //强转http请求
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
//从请求头获取数据
//定死了名称为 authorization
String tokenStr = httpServletRequest.getHeader("authorization");
System.out.println(tokenStr);
/*
打印结果 【不可换行,这里为了展示才换行】
Bearer eyJhbGciOiJIUzUxMiJ9.eyJhdXRob3JpdGllcyI6IlJPTEVfYWRtaW4sIiwic3ViIjoiYWRtaW4iLCJleHAi
OjE1OTE2MzAwMTF9.oHmTl-f5RetmFJ8rM5MaIruOkA83sqt-6F7f2c27QRWdJvTAOIYX_VbRCngodaROZ4jprQ1ktwz5sZAWcDJdkg */ System.out.println("==========================================");
if (tokenStr != null) {
System.out.println("有认证令牌");
boolean k = true;
Jws<Claims> jws = null;
try {
//解析,解析方式使用加密时配置的数字签名对应
//一旦令牌修改成位数对比不上,会报错。。。
jws = Jwts.parser().setSigningKey("java521@java")
.parseClaimsJws(tokenStr.replace("Bearer", ""));
System.out.println(tokenStr.replace("Bearer", ""));
/*
打印结果 【不可换行,这里为了展示才换行】
eyJhbGciOiJIUzUxMiJ9.eyJhdXRob3JpdGllcyI6IlJPTEVfYWRtaW4sIiwic3ViIjoiYWRtaW4iLCJleHAiOjE
1OTE2MzAwMTF9.oHmTl-f5RetmFJ8rM5MaIruOkA83sqt-6F7f2c27QRWdJvTAOIYX_VbRCngodaROZ4jprQ1ktwz5sZAWcDJdkg
*/
} catch (Exception e) {
//放令牌被修改、时间过期,都会抛出异常,由方法 parseClaimsJws()安抛出的异常
// e.printStackTrace();
k = false;
}
if (k) {
// 令牌解析成功
Claims claims = jws.getBody();
//获取token解析出来的用户名
String username = claims.getSubject();
System.out.println(username);
/*
打印结果
[ROLE_admin,ROLE_user,等等]
*/
//从token获取登录角色的权限
//如果时以逗号格式配置字符串,可用以下方式解析,否则手动解析
List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get("authorities"));
System.out.println(grantedAuthorities);
// //new令牌登录校验 对象,参数分别是 : 用户名 ,盐[没有则设为null] ,角色/权限
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, grantedAuthorities);
//执行令牌登录校验
SecurityContextHolder.getContext().setAuthentication(token);
} else {
System.out.println("令牌解析失败,被修改了");
SecurityContextHolder.getContext()
.setAuthentication(new UsernamePasswordAuthenticationToken(null, null, null));
}
} else {
System.out.println("没有认证令牌");
SecurityContextHolder.getContext()
.setAuthentication(new UsernamePasswordAuthenticationToken(null, null, null));
}
System.out.println("//让过滤器继续往下走,");
//让过滤器继续往下走
filterChain.doFilter(servletRequest, servletResponse); }
} /*
总结:
1.生成token 的变化数据是用户名和权限拼接的字符串 ,其他的固定
2.生成的token是将登录通过的用户的权限拼接的字符串加密后放入里面后加密,当携带token访问时被拦截后,会将token解析出的权限注册,因为不与数据库等数据共享校验权限最新信息,
如果在携带token的请求前权限有变化,但是token却没有改变,会导致token权限与用户真实权限不一致,形成脏数据啦!!!
如果权限增加还好,使得无法访问新加权限的操作,如果是减少权限,比如vip过期,用户仍然可以有vip权限。
3.解决token脏数据的方案有两个:
(1)等待该token失效时间【不靠谱】;
(2)每次修改权限时,会强制使得token失效,具体怎么做,还没试过
4.当然,也有优点的,不与数据库等最新数据做权限对比操作,较少了访问数据库该用户信息的部分,能快速的过滤请求权限,理论上访问数据会变快。
5.可以设置过期时间,单位毫秒,用时间戳设置 ,到时间则不可在使用,
但是缺点很明显,在未过期之前,可以无数次访问验证通过,无法控制使用次数,
因此不能作为资源服务器对第三方应用开放的授权令牌,
6.令牌格式对不上,会直接报错异常,为了服务降级,做个异常捕获即可
7.如果生成了新的令牌,旧的令牌仍然可以使用,因此会导致多设备同时登录的情况,无法控制登录数量
8.使用jwt[java web token],做登录校验,则会导致http.sessionManagement().maximumSessions(1);设置失效,因为没有使用session做为登录控制
//
安全弊端很多 , 但是让我深刻明白了token的内部思想
*/

(6)服务层根据用户名获取用户信息【为了简便,没有使用数据库,直接赋值】

package com.example.securityjwt5605.filters;

import com.example.securityjwt5605.model.JwtUser;
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.Service; import java.util.ArrayList;
import java.util.List; /**
* 这个类其实就是为了获取用户的正确认证信息,不做信息比较,比较是在过滤器里面,
* 名字叫做 UsernamePasswordAuthenticationFilter
*/ @Service
public class MyUserDetailsService implements UserDetailsService { @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
JwtUser tUser = new JwtUser();
//权限设置
List<GrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
System.out.println("===============================数据库层对比=================================="); if (username.equals("cen")) {
tUser.setUsername(username);
tUser.setPassword("$2a$10$Qghi7vHdyQJHYlAO.FCo/u3gCbqwWBVaSHjIF0Vci.C5.1l71SExq");
simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ROLE_user"));
tUser.setGrantedAuthorities(simpleGrantedAuthorities);
} else if (username.equals("admin")) {
tUser.setUsername(username);
tUser.setPassword("$2a$10$ywq3gn6E15tnY3URptsIz.zn/fznWGqc2VhO4zphS/sIbWZJtLCVK");
simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ROLE_admin"));
tUser.setGrantedAuthorities(simpleGrantedAuthorities);
} else {
throw new UsernameNotFoundException("没有找到用户");
} // System.out.println("=============================");
// //根据用户名去数据库查询用户信息
// TUser tUser = userService.getByUsername(username);
// if (tUser == null){
// throw new UsernameNotFoundException("用户不存在!");
// }
// //权限设置
// List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
// String role = tUser.getRole();
// //分割权限名称,如 user,admin
// String[] roles = role.split(",");
// System.out.println("=============================");
// System.out.println("注册该账户权限");
// for (String r :roles){
// System.out.println(r);
// //添加权限
// simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ROLE_"+r));
//// simpleGrantedAuthorities.add(new SimpleGrantedAuthority(r));
// }
// tUser.setGrantedAuthorities(simpleGrantedAuthorities);
// System.out.println("============================="); /**
* 创建一个用于认证的用户对象,包括:用户名,密码,权限
*
*/
//输入参数
// return new org.springframework.security.core.userdetails.User(tUser.getUsername(), tUser.getPassword(), simpleGrantedAuthorities); // 这个返回值的类型,继承了userdetails即可
return tUser; } }

(7)contoller接口

package com.example.securityjwt5605.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import java.util.HashMap;
import java.util.Map; @RestController
public class HHController { //开启跨域
// [普通跨域]
//@CrossOrigin
//[spring security 跨域]
@CrossOrigin(allowCredentials = "true", allowedHeaders = "*")
@RequestMapping("/hello")
public Map<String, Object> hello() {
Map<String, Object> map = new HashMap<>();
map.put("data", "hello");
return map;
} //开启跨域
// [普通跨域]
//@CrossOrigin
//[spring security 跨域]
@CrossOrigin(allowCredentials = "true", allowedHeaders = "*")
@RequestMapping("/admin")
public Map<String, Object> admin() {
Map<String, Object> map = new HashMap<>();
map.put("data", "i am admin");
return map;
} }

(8)security配置类

package com.example.securityjwt5605.config;

import com.example.securityjwt5605.filters.JwtFilter;
import com.example.securityjwt5605.filters.JwtLoginFilter;
import com.example.securityjwt5605.filters.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.lang.reflect.Method; @Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
private MyUserDetailsService myUserDetailsService; /**
* 全局的跨域配置
*/
@Bean
public WebMvcConfigurer WebMvcConfigurer() {
return new WebMvcConfigurer() {
public void addCorsMappings(CorsRegistry corsRegistry) {
//仅仅让/login可以跨域
corsRegistry.addMapping("/login").allowCredentials(true).allowedHeaders("*");
//仅仅让/logout可以跨域
corsRegistry.addMapping("/logout").allowCredentials(true).allowedHeaders("*");
//允许所有接口可以跨域访问
//corsRegistry.addMapping("/**").allowCredentials(true).allowedHeaders("*"); }
}; } /**
* 忽略过滤的静态文件路径
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/js/**/*.js",
"/css/**/*.css",
"/img/**",
"/html/**/*.html"
);
} //内存放入可登录的用户信息
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
System.out.println("===============================认证管理构造器=================================="); //直接注册信息到内存,会导致jrebel热更新失效,无法更新该内容
//
//如果仅仅设置了roles,则权限自动设置并自动添加前缀 为 ROLE_【角色内部的字符串,可以设置多个】,
//字符串不可再添加ROLE_,会报java.lang.IllegalArgumentException: ROLE_user cannot start with ROLE_ (it is automatically added)
//意思是用 ROLE_前缀会自动添加,
// auth.inMemoryAuthentication().withUser("cen")
// .password("$2a$10$Qghi7vHdyQJHYlAO.FCo/u3gCbqwWBVaSHjIF0Vci.C5.1l71SExq").roles("user")
// //如果使用了roles 和 authorities ,那么roles将失效,将会注册authorities内部的字符串为权限,且不会添加前缀名ROLE_
// .and().withUser("admin")
// .password("$2a$10$ywq3gn6E15tnY3URptsIz.zn/fznWGqc2VhO4zphS/sIbWZJtLCVK").roles("user").authorities("ROLE_admin");
// //
//因此用户cen的权限为ROLE_user
//用户admin的权限为 admin //
//
//调用数据库层,根据用户名获取用户信息回来,
auth.userDetailsService(myUserDetailsService)
//设置加密方式
.passwordEncoder(passwordEncoder()); } @Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} //过滤规则,一旦设置了重写了这个方法,必须设置登录配置
//在启动的时候就执行了
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("===============================过滤规则=================================="); http.authorizeRequests()
.antMatchers("/hello").hasRole("user")
.antMatchers("/admin").hasRole("admin")
// .antMatchers("/admin").hasAuthority("admin")
//当访问/login的请求方式是post才允许通过
.antMatchers(HttpMethod.POST, "/login").permitAll()
// .anyRequest()
.anyRequest().authenticated()
.and()
//首次登录拦截。仅允许post访问/login
.addFilterBefore(new JwtLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
//token验证拦截
.addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class)
//
.cors()
.and()
.csrf().disable();
//
//使用jwt[java web token],做登录校验,则该设置失效,因为没有使用session做为登录控制
// http.sessionManagement().maximumSessions(1); } }

(9)同时做了简易的前端访问页面【前后端分离,前端端口是5601 ,后端是5605】

jwt.html

<!DOCTYPE html>
<html lang="zh" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>[JWT] 测试</title>
</head>
<body>
jave web token [JWT] 测试
<hr>
<div>
<label>
账户:
<input id="name" type="text">
</label>
<label>
密码:
<input id="psw" type="text">
</label>
</div>
<button onclick="dosome(1)">登录</button>
<hr>
<hr>
<button onclick="dosome(3)">登出</button>
<hr>
<label>
token:
<input id="url" type="text" value="http://localhost:5605/hello">
<span id="token"></span>
</label>
<button onclick="dotoken()">点我token访问</button>
<hr>
返回的结果:<span id="res"></span> <!--当前路径是/html/** ,因此需要返回一级 ,所以用 ../js/ -->
<script src="../js/jquery-1.11.1.min.js"></script>
<script src="../js/base64.js"></script> <script>
//当前最新的token
let token = ""; function dotoken() {
let url = ""+($("#url").val()).trim();
if (url == ""){
console.log("url不可空")
return;
}
$.ajax({
//请求头添加token
//方法一:
// beforeSend: function (request) {
// request.setRequestHeader("Authorization", token);
// },
//方法二:
headers: {
//认证信息
Authorization: token
},
async: true,
type: 'post',
dataType: "json",
url: url,
xhrFields: {withCredentials: true}, //前端适配:允许session跨域
crossDomain: true, success: function (data) {
console.log(data);
//请求成功回调函数
if (data != null) {
// alert("有数据返回")
$("#res").html(JSON.stringify(data))
} else {
alert("系统异常")
}
},
error: function (xhr, type, errorThrown) {
//异常处理;
console.log("异常处理")
console.log(JSON.stringify(xhr));
if (xhr.readyState == 4 && xhr.status == 403){
$("#res").html("403无权访问")
}
/*
{"readyState":4,"responseText":"{\"timestamp\":\"2020-06-08T16:51:15.016+00:00\",
\"status\":403,\"error\":\"Forbidden\",\"message\":\"\",\"path\":\"/admin\"}",
"responseJSON":{"timestamp":"2020-06-08T16:51:15.016+00:00","status":403,"error":"Forbidden",
"message":"","path":"/admin"},"status":403,"statusText":"error"}
*/
console.log(type);
console.log(errorThrown);
}
});
} function dosome(type) {
let name = "";
let psw = "";
let url = "";
if (type == 1) {
name = ($("#name").val()).trim();
psw = ($("#psw").val()).trim();
//登录
url = "http://localhost:5605/login";
} else if (type == 3) {
//登出
url = "http://localhost:5605/logout";
}
//URL是URI的子集,所有的URL都是URI,但不是每个URI都是URL,还有可能是URN。
$.ajax({
async: true,
type: 'post',
//对应于后端 parama 方式获取数据 ,使用req.getParameter获取
// data: {"username": name, "password": psw},
//对应于后端raw方式获取数据,需要json解析,使用req.getInputStream()获取
data: JSON.stringify({"username": name, "password": psw}),
//这里类型是json,那么跨域的后端需要是map类型、po实体类等 json类型 才能接收数据
dataType: "json",
url: url,
xhrFields: {withCredentials: true}, //前端适配:允许session跨域
crossDomain: true,
// //请求头设置
// headers: {
// //认证信息
// Authorization: authorization
// },
success: function (data) {
console.log(data);
//请求成功回调函数
if (data != null) {
// alert("有数据返回")
$("#res").html(JSON.stringify(data))
token = data.token;
$("#token").html(token);
} else {
alert("系统异常")
}
},
error: function (xhr, type, errorThrown) {
//异常处理;
console.log("异常处理")
console.log(JSON.stringify(xhr));
console.log(type);
console.log(errorThrown);
}
});
} </script>
</body>
</html>

3.测试

token时间设长一点,我这里设为1小时

(1)使用postman f访问  http://localhost:5605/login 进行登录

登录成功

获取令牌,访问  http://localhost:5605/hello

提交后

成功

因为用户 cen我设置了只有权限。

再次访问 http://localhost:5605/admin

无权限403被拒绝了

事实上,当令牌过期后再访问,也会抛出403结果

换一个账户

访问  http://localhost:5605/admin ,是可以访问的

(2)测试前端 跨域 访问

测试密码错误

测试登录成功

点击token访问

token 成功

换一个没有访问hello权限的账号

然后再次点击token访问

4.过滤器的先后操作

(1)工程启动,控制台打印

(2)用户名密码登录后,控制台打印

(3)携带token访问

有效的令牌

无效的令牌

奇怪的是 携带无效令牌时 会执行两次token访问过滤器,原因还不清楚

-------------------------

参考博文原址:

https://mp.weixin.qq.com/s/Sn59dxwtsEWoj2wdynQuRQ

spring boot + spring security +JWT令牌 +前后端分离--- 心得的更多相关文章

  1. 基于Spring Boot+Spring Security+JWT+Vue前后端分离的开源项目

    一.前言 最近整合Spring Boot+Spring Security+JWT+Vue 完成了一套前后端分离的基础项目,这里把它开源出来分享给有需要的小伙伴们 功能很简单,单点登录,前后端动态权限配 ...

  2. Spring Security + JWT实现前后端分离权限认证

    现在国内前后端很多公司都在使用前后端分离的开发方式,虽然也有很多人并不赞同前后端分离,比如以下这篇博客就很有意思: https://www.aliyun.com/jiaocheng/650661.ht ...

  3. 手把手教你使用 Spring Boot 3 开发上线一个前后端分离的生产级系统(一) - 介绍

    项目简介 novel 是一套基于时下最新 Java 技术栈 Spring Boot 3 + Vue 3 开发的前后端分离的学习型小说项目,配备详细的项目教程手把手教你从零开始开发上线一个生产级别的 J ...

  4. 全栈的自我修养: 001环境搭建 (使用Vue,Spring Boot,Flask,Django 完成Vue前后端分离开发)

    全栈的自我修养: 环境搭建 Not all those who wander are lost. 彷徨者并非都迷失方向. Table of Contents @ 目录 前言 环境准备 nodejs v ...

  5. Spring Cloud实战 | 最八篇:Spring Cloud +Spring Security OAuth2+ Axios前后端分离模式下无感刷新实现JWT续期

    一. 前言 记得上一篇Spring Cloud的文章关于如何使JWT失效进行了理论结合代码实践的说明,想当然的以为那篇会是基于Spring Cloud统一认证架构系列的最终篇.但关于JWT另外还有一个 ...

  6. 从零玩转SpringSecurity+JWT整合前后端分离

    从零玩转SpringSecurity+JWT整合前后端分离 2021年4月9日 · 预计阅读时间: 50 分钟 一.什么是Jwt? Json web token (JWT), 是为了在网络应用环境间传 ...

  7. SpringBoot使用SpringSecurity搭建基于非对称加密的JWT及前后端分离的搭建

    SpringBoot使用SpringSecurity搭建基于非对称加密的JWT及前后端分离的搭建 - lhc0512的博客 - CSDN博客 https://blog.csdn.net/lhc0512 ...

  8. VUE开发(一)Spring Boot整合Vue并实现前后端贯穿调用

    文章更新时间:2020/03/14 一.前言 作为一个后端程序员,前端知识多少还是要了解一些的,vue能很好的实现前后端分离,且更便于我们日常中的调试,还具备了轻量.低侵入性的特点,所以我觉得是很有必 ...

  9. Shrio使用Jwt达到前后端分离

    概述 前后端分离之后,因为HTTP本身是无状态的,Session就没法用了.项目采用jwt的方案后,请求的主要流程如下:用户登录成功之后,服务端会创建一个jwt的token(jwt的这个token中记 ...

随机推荐

  1. BigDecimal中要注意的一些事

    一.关于public BigDecimal(double val) BigDecimal中三个主要的构造函数 1 public BigDecimal(double val) 将double表示形式转换 ...

  2. 1.ElasticSearch相关概念

    1.为ElasticSearch设置跨域访问 http.cors.enabled: truehttp.cors.allow-origin: "*" 2.什么是ElasticSear ...

  3. 网络协议之:基于UDP的高速数据传输协议UDT

    目录 简介 UDT协议 UDT的缺点 总结 简介 简单就是美.在网络协议的世界中,TCP和UDP是建立在IP协议基础上的两个非常通用的协议.我们现在经常使用的HTTP协议就是建立在TCP协议的基础上的 ...

  4. vue3 到底哪里好?看这一篇就够了

    之前写的关于 vue3 的文章,好多人吐槽:这些API每次使用都要引入一遍,感觉有点麻烦. 今天我们就来看看 vue3 相比 vue2 的优点有些啥? 为啥有些人说:自从写了 ts vue3 再也回不 ...

  5. 背包问题-C语言实现

    转自:http://blog.csdn.net/tjyyyangyi/article/details/7929665 0-1背包问题 参考: http://blog.csdn.net/liwenjia ...

  6. not_the_same_3dsctf_2016

    老样子查看程序开启的保护 可以看到程序是32位的程序开启了nx保护,把程序放入ida编译一下 shift+f12可以看到flag.txt,我们用ctrl+x跟随一下 看到程序,直接想到的就是通过溢出获 ...

  7. vue3官网介绍,安装,创建一个vue实例

    前言:这一章主要是vue的介绍.安装.以及如何创建一个vue实例. 一.vue介绍 vue3中文官网:建议先自己看官网. https://v3.cn.vuejs.org/ vue是渐进式框架,渐进式指 ...

  8. MyBatis 3学习笔记

    MyBatis 3 一.MyBatis简介 优秀的持久层框架,支持支持自定义 SQL.存储过程以及高级映射,专注于SQL的编写. ​ 为什么不使用工具类进行数据库操作: ​ 功能简单,sql语句编写在 ...

  9. C#汉字转汉语拼音

    一.使用PinYinConverterCore获取汉语拼音 最新在做一个搜索组件,需要使用汉语拼音的首字母查询出符合条件的物品名称,由于汉字存在多音字,所以自己写查询组件不太现实,因此,我们使用微软提 ...

  10. CF336A Vasily the Bear and Triangle 题解

    Content 一个矩形的顶点为 \((0,0)\),其对顶点为 \((x,y)\),现过 \((x,y)\) 作直线,分别交 \(x\) 轴和 \(y\) 轴于 \(A,B\) 两点,使得 \(\t ...