JWT 教程
...2021年10月12日大约 11 分钟
文章链接
https://gitee.com/fakerlove/jwt
JWT 教程
1. 介绍
https://jwt.io/
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
2. SpringBoot与JWT整合
2.1 JWT的结构:
Header(头):包含令牌的类型与使用的签名算法,它会使用Base64进行编码
{
"alg": "HS265",
"typ": "JWT"
}
Payload(有效负载): 包含声明(有关用户实体和其他数据的声明),使用Base64进行编码
Base64是一种可逆的编码,因此不要在负载里存入敏感数据!
{
"id": "1",
"name": "BLU",
"admin": true
}
Signature(签名):使用编码后的header和payload以及一个指定密钥,然后使用header中指定的算法(HS265)进行签名.
签名的作用是保证JWT没有被篡改过
负载
iss: jwt 的签发者
sub: jwt 所面向的用户
aud:接受jwt 的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf:定义在什么时间之前,该jwt 不可用的
iat: jwt的签发时间
jti:jwt 的唯一身份识别,主要用来作为一次性token,从而回避重放攻击
有很多公司在做jwt ,因为jwt 是标准,并不是实现,所以需要公司实现,比较有名的
auth0,java-jwt,jjwt
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.12.0</version>
</dependency>
2.2 JWT的使用测试:
1) java-jwt 版本
- 依赖:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
- 测试:
@Test
void getToken() {
//HashMap<String,Object> map = new HashMap<String, Object>();
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, 60);
String token = JWT.create()
//.withHeader(map)
//设置Payload
.withClaim("userId", 10)
.withClaim("username", "BLU")
//设置token过期时间(60s)
.withExpiresAt(instance.getTime())
//设置签名
.sign(Algorithm.HMAC256("!@#$%^&*"));
System.out.println(token);
}
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDA3NDA2MTUsInVzZXJJZCI6MTAsInVzZXJuYW1lIjoiQkxVIn0.0re-tA4dQm4blGhn1DvpnUl7Lrz_EWXwn8LfRbWQXCU
@Test
void TokenVerify() {
//创建验证对象
JWTVerifier verifier = JWT.require(Algorithm.HMAC256("!@#$%^&*")).build();
DecodedJWT verify = verifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDA3NDA2MTUsInVzZXJJZCI6MTAsInVzZXJuYW1lIjoiQkxVIn0.0re-tA4dQm4blGhn1DvpnUl7Lrz_EWXwn8LfRbWQXCU");
System.out.println(verify.getClaim("userId").asInt());
System.out.println(verify.getClaims().get("username").asString());
System.out.println("过期时间:"+verify.getExpiresAt());
}
10
BLU
过期时间:Tue Sep 22 10:10:15 CST 2020
2) jjwt 版本
<?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.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo3</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<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>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
验证
package com.example.demo3;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
@SpringBootTest
class Demo3ApplicationTests {
@Test
void contextLoads() {
JwtBuilder jwtBuilder= Jwts.builder()
// 用户的id
.setId("001")
// 接受用户的角色信息
.setSubject("Rose")
// 签发时间
.setIssuedAt(new Date())
// 采用的算法
.signWith(SignatureAlgorithm.HS256,"xxxx");
String token=jwtBuilder.compact();
System.out.println(token);
}
}
结果
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIwMDEiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjA5NTcxNzg1fQ.pqau27EgQhVtqamer2_8z-gBQeATGNveBuUAyVlSac4
检验
@Test
public void verfity(){
String token="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIwMDEiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjA5NTcxNzg1fQ.pqau27EgQhVtqamer2_8z-gBQeATGNveBuUAyVlSac4";
Claims claims= Jwts.parser()
.setSigningKey("xxxx")
.parseClaimsJws(token)
.getBody();
System.out.println(claims.getId());
System.out.println(claims.getSubject());
System.out.println(claims.getIssuedAt());
}
结果
001
Rose
Sat Jan 02 15:16:25 CST 2021
过期时间
@Test
void contextLoads() {
JwtBuilder jwtBuilder= Jwts.builder()
// 用户的id
.setId("001")
// 接受用户的角色信息
.setSubject("Rose")
// 签发时间
.setIssuedAt(new Date())
// 采用的算法
.signWith(SignatureAlgorithm.HS256,"xxxx")
.setExpiration(new Date(System.currentTimeMillis()+60*1000));
String token=jwtBuilder.compact();
System.out.println(token);
}
自定义claim
package com.example.demo3;
import io.jsonwebtoken.*;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
@SpringBootTest
class Demo3ApplicationTests {
@Test
void contextLoads() {
JwtBuilder jwtBuilder= Jwts.builder()
// 用户的id
.setId("001")
// 接受用户的角色信息
.setSubject("Rose")
// 签发时间
.setIssuedAt(new Date())
// 采用的算法
.signWith(SignatureAlgorithm.HS256,"xxxx")
// .setExpiration(new Date(System.currentTimeMillis()+60*1000))
.claim("name","joker")
.claim("login","sadas");
String token=jwtBuilder.compact();
System.out.println(token);
}
@Test
public void verfity(){
String token="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIwMDEiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjA5NTcyODU4LCJuYW1lIjoiam9rZXIiLCJsb2dpbiI6InNhZGFzIn0.-Z2YMakhk6pKa27xyEFgGb5Jjm0ebMUEMpLSFA42c7A";
Claims claims= Jwts.parser()
.setSigningKey("xxxx")
.parseClaimsJws(token)
.getBody();
System.out.println(claims.getId());
System.out.println(claims.getSubject());
System.out.println(claims.getIssuedAt());
System.out.println(claims.get("name"));
System.out.println(claims.get("login"));
}
}
2.3 SpringBoot与JWT整合:
- 依赖
<dependencies>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</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>
</dependencies>
- 配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/jwt?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456
mybatis.type-aliases-package=com.blu.entity
mybatis.mapper-locations=classpath:mapper/*.xml
logging.level.com.blu.dao=debug
- 数据库user表:
- 实体类:
package com.blu.entity;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain=true)
public class User {
private String id;
private String name;
private String password;
}
- UserDao:
package com.blu.dao;
import org.apache.ibatis.annotations.Mapper;
import com.blu.entity.User;
@Mapper
public interface UserDao {
User login(User user);
}
- UserMapper:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.blu.dao.UserDao">
<select id="login" parameterType="User" resultType="User">
select * from user where name= #{name} and password= #{password}
</select>
</mapper>
- UserService:
package com.blu.service;
import com.blu.entity.User;
public interface UserService {
User login(User user);
}
- UserServiceImpl:
package com.blu.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.blu.dao.UserDao;
import com.blu.entity.User;
import com.blu.service.UserService;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public User login(User user) {
User userDB = userDao.login(user);
if(userDB!=null) {
return userDB;
}
throw new RuntimeException("认证失败!");
}
}
- JWT的工具类封装:
package com.blu.utils;
import java.util.Calendar;
import java.util.Map;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
public class JWTUtils {
private static final String SIGN= "!@#$%^&*123456789";
/**
* 生成Token
*/
public static String getToken(Map<String,String> map) {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE, 7);
//创建JWTBuilder
JWTCreator.Builder builder = JWT.create();
//设置payload
map.forEach((k,v)->{
builder.withClaim(k, v);
});
//设置过期时间和签名,生成token
String token = builder.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(SIGN));
return token;
}
/**
* 验证token
*/
public static void TokenVerify(String token) {
JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
}
/**
* 获取token信息
*/
public static DecodedJWT getTokenInfo(String token) {
DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
return verify;
}
}
- JWTInterceptor 拦截器
package com.blu.interceptors;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.blu.utils.JWTUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//获取请求头中的token
String token = request.getHeader("token");
Map<String,Object> map = new HashMap<String, Object>();
try {
JWTUtils.TokenVerify(token);
//放行请求
return true;
} catch (SignatureVerificationException e) {
map.put("msg", "无效签名");
e.printStackTrace();
} catch (TokenExpiredException e) {
map.put("msg", "token已过期");
e.printStackTrace();
} catch (AlgorithmMismatchException e) {
map.put("msg", "算法不一致");
e.printStackTrace();
} catch (Exception e) {
map.put("msg", "token无效");
e.printStackTrace();
}
map.put("state",false);
//使用jackson将map转为json
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().print(json);
return false;
}
}
- InterceptorConfig 配置类
package com.blu.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.blu.interceptors.JWTInterceptor;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/user/login");
}
}
- UserController
package com.blu.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.blu.entity.User;
import com.blu.service.UserService;
import com.blu.utils.JWTUtils;
import lombok.extern.slf4j.Slf4j;
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user/login")
public Map<String,Object> login(User user){
log.info("用户名:[{}]",user.getName());
log.info("密码:[{}]",user.getPassword());
Map<String,Object> map = new HashMap<String, Object>();
try {
User userDB = userService.login(user);
Map<String,String> payload = new HashMap<String, String>();
payload.put("id", userDB.getId());
payload.put("name", userDB.getName());
String token = JWTUtils.getToken(payload);
map.put("state",true);
map.put("msg","认证成功");
map.put("token", token);
} catch (Exception e) {
map.put("state",false);
map.put("msg",e.getMessage());
}
return map;
}
@PostMapping("/user/test")
public Map<String,Object> test(HttpServletRequest request){
String token = request.getHeader("token");
DecodedJWT tokenInfo = JWTUtils.getTokenInfo(token);
log.info("用户id:[{}]",tokenInfo.getClaim("id").asString());
log.info("用户名:[{}]",tokenInfo.getClaim("name").asString());
Map<String,Object> map = new HashMap<String, Object>();
map.put("state", true);
map.put("msg","请求成功");
return map;
}
}
- 测试
登录获取token:测试错误的token:
测试正确的token:
3. SpringSecurity与JWT整合
依赖:
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
</dependencies>
application.properties:
spring.thymeleaf.cache=false
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springsecurity001?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
mybatis.mapper-locations=classpath:mapper/*.xml
启动类SecurityJwtApplication:
@SpringBootApplication
@MapperScan("com.blu.mapper")
public class SecurityJwtApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityJwtApplication.class, args);
}
@Bean
public BCryptPasswordEncoder bcryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
MyUser:
@Data
public class MyUser implements UserDetails {
private Integer id;
private String name;
private String password;
private List<MyRole> roles;
@JsonIgnore
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles;
}
@JsonIgnore
@Override
public String getUsername() {
return name;
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
}
MyRole:
@Data
public class MyRole implements GrantedAuthority {
private Integer id;
private String name;
@JsonIgnore
@Override
public String getAuthority() {
return name;
}
}
MyUserMapper:
public interface MyUserMapper {
MyUser findByName(String name);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.blu.mapper.MyUserMapper">
<resultMap type="com.blu.entity.MyUser" id="myUserMap">
<id column="uid" property="id"></id>
<result column="uname" property="name"></result>
<result column="password" property="password"></result>
<collection property="roles" ofType="com.blu.entity.MyRole">
<id column="rid" property="id" />
<result column="rname" property="name" />
</collection>
</resultMap>
<select id="findByName" parameterType="String"
resultMap="myUserMap">
select u.id uid,u.name uname,u.password,r.id rid,r.name rname
from user u,role r,role_user ur
where u.name = #{name} and ur.user_id = u.id and ur.role_id = r.id
</select>
</mapper>
UserService:
public interface UserService extends UserDetailsService {
}
UserServiceImpl:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private MyUserMapper myUserMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
MyUser myUser = myUserMapper.findByName(username);
return myUser;
}
}
JwtLoginFilter:
public class JwtLoginFilter extends AbstractAuthenticationProcessingFilter {
public JwtLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {
super(new AntPathRequestMatcher(defaultFilterProcessesUrl));
setAuthenticationManager(authenticationManager);
}
/**
* 从登录参数json数据中获取用户名密码,然后调用AuthenticationManager.authenticate()方法进行校验。
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse resp) throws AuthenticationException, IOException, ServletException {
//将用户传的json数据转为user对象
MyUser user = new ObjectMapper().readValue(req.getInputStream(),MyUser.class);
return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword()));
}
/**
* 校验成功
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
StringBuffer sb = new StringBuffer();
for (GrantedAuthority authority : authorities) {
sb.append(authority.getAuthority()).append(",");
}
String jwt = Jwts.builder()
.claim("authorities", sb)
.setSubject(authResult.getName())
.setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 1000))//设置过期时间
.signWith(SignatureAlgorithm.HS512, "root@123")//设置加密方式,以及key
.compact();
//设置登录成功后返回的信息
Map<String,String> map = new HashMap<>();
map.put("token",jwt);
map.put("msg","登录成功");
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(new ObjectMapper().writeValueAsString(map));
writer.flush();
writer.close();
}
/**
* 校验失败
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
Map<String,String> map = new HashMap<>();
map.put("msg","登录失败");
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(new ObjectMapper().writeValueAsString(map));
writer.flush();
writer.close();
}
}
JwtFilter:
public class JwtFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
//从请求头中获取token
String jwtToken = req.getHeader("authorization");
Jws<Claims> jws = Jwts.parser().setSigningKey("root@123")
.parseClaimsJws(jwtToken.replace("Bearer", ""));
Claims claims = jws.getBody();
String username = claims.getSubject();
//获取角色
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get("authorities"));
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username,"",authorities);
SecurityContextHolder.getContext().setAuthentication(token);
filterChain.doFilter(servletRequest,servletResponse);
}
}
测试接口:
@RestController
public class StringController {
@GetMapping("hello")
public String hello() {
return "hello BLU!";
}
@GetMapping("hi")
public String hi() {
return "hi everyone!";
}
@GetMapping("admin")
public String admin() {
return "hello admin!";
}
}
Security配置类:
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private UserServiceImpl userServiceImpl;
@Autowired
private BCryptPasswordEncoder bcryptPasswordEncoder;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.GET,"/hi").permitAll()
.antMatchers("/hello").hasRole("vip1")
.antMatchers("/admin").hasRole("admin");
http.formLogin().loginPage("/tologin")
.usernameParameter("name")
.passwordParameter("password")
.loginProcessingUrl("/login");
http.csrf().disable();
http.addFilterBefore(new JwtLoginFilter("/login",authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JwtFilter(),UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userServiceImpl).passwordEncoder(bcryptPasswordEncoder);
}
}
测试错误密码登录: 测试正确密码登录:
测试访问(有权限):
测试访问(无权限):
测试访问(错误token):
4. oauth和JWT 整合
编写jwt 类
package com.example.demo3.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.jwt.Jwt; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; @Configuration public class JwtTokenStoreConfig { @Bean public TokenStore tokenStore(){ return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter jwtAccessTokenConverter=new JwtAccessTokenConverter(); jwtAccessTokenConverter.setSigningKey("xxxx"); return jwtAccessTokenConverter; } }
编写服务器类
package com.example.demo3.config; import com.example.demo3.service.LoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; /** * 这个是授权服务器 * 功能类似于 微信,qq 等大场企业 */ @Configuration @EnableAuthorizationServer public class AuthorizationServiceConfig extends AuthorizationServerConfigurerAdapter { @Autowired private PasswordEncoder encoder; @Autowired private AuthenticationManager authenticationManager; @Autowired private TokenStore jwtTokenStore; @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Autowired LoginService service; @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .userDetailsService(service) .tokenStore(jwtTokenStore) .accessTokenConverter(jwtAccessTokenConverter); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("wechat") .secret(encoder.encode("112233")) // 秘钥 .redirectUris("https://www.baidu.com") //重定向服务器配置 .scopes("all")// 授权范围 .authorizedGrantTypes("authorization_code","password"); } }
编写服务器类
package com.example.demo3.config; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; /** * 这个是类似于 微信 qq 等大场的资源 */ @Configuration @EnableResourceServer public class ResourceConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable(); http.authorizeRequests() .anyRequest() .authenticated() .and() .requestMatchers() .antMatchers("/user/**"); } }
编写config
package com.example.demo3.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; 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.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/myLoginController") // 一定要和 Controller 中 返回 myLogin页面 一致, .loginProcessingUrl("/login");// 必须和表单提交 action 的名字 一样的,提交 username 和password // .successForwardUrl("/toSuccess");// 这个是 登录成功后返回的界面 http.authorizeRequests() .antMatchers("/myLoginController").permitAll()// 放行myLoginController .antMatchers("/oauth/**","/login/**","/logout/**").permitAll() .anyRequest().authenticated(); http.csrf().disable(); } @Override @Bean public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
输入
package com.example.demo3.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class LoginDemo { @RequestMapping("/myLoginController") public String myLogin(){ return "myLogin"; } @RequestMapping("/toSuccess") public String toSuccess(){ return "success"; } @RequestMapping("/toFail") public String toFail(){ return "fail"; } }
资源网
package com.example.demo3.controller; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") public class UserController { @RequestMapping("/getCurrentUser") public Object kk(Authentication authentication){ return authentication.getPrincipal(); } }
userDetails
package com.example.demo3.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @Service public class LoginService implements UserDetailsService { @Autowired PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 自定义 用户登录名 if(!username.equals("admin")){ throw new UsernameNotFoundException("这是俺自己写的错误"); } String password=passwordEncoder.encode("123"); return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_abc")); } }