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

最新文章

  1. jsb里出现的 Invalid Native Object的一次bug修复的思考
  2. *IDEA真好用
  3. RIA(富客户端)发展态势
  4. Unity3d Shader开发(二)SubShader
  5. Arcgis Android 基本概念 - 浅谈
  6. C/C++将一个整型数组拼接成一个字符串
  7. CVE-2017-11882漏洞 Msf利用复现
  8. freemarker报错之九
  9. 简单测试Elasticsearch 7.0
  10. phpstudy设置允许远程访问mysql数据库
  11. Maven(六)Eclipse使用Maven插件创建项目
  12. 探究Java中的锁
  13. eclipse 打包
  14. Heaven of Imaginary(PKUSC2018)
  15. Spring+MyBatis实现数据库读写分离方案
  16. iOS进阶_Socket(Socket简介&amp;代码演练)
  17. JavaScript总结(六)
  18. 28_数据库_第28天(数据库、表及表数据、SQL语句)_讲义
  19. FM的推导原理--推荐系统
  20. JavaScript网页换肤

热门文章

  1. 13.Vue.js 组件
  2. maven依赖对zookeeper的版本冲突问题
  3. 【划重点】Python matplotlib绘图设置坐标轴的刻度
  4. A New Discrete Particle Swarm Optimization Algorithm
  5. WPF控件界面自适应
  6. Linux(centos7)安装ClickHouse
  7. c++之别让异常逃离析构函数
  8. Exploration(hdu5222)
  9. Mind the Box: $\ell_1$-APGD for Sparse Adversarial Attacks on Image Classifiers
  10. ADAM : A METHOD FOR STOCHASTIC OPTIMIZATION