详解Java token主流框架之JWT
作者:蜀山剑客李沐白 发布时间:2022-03-30 19:30:34
1. JWT的概念和特点
JWT是一种轻量级、可扩展、可自包含的身份验证和授权机制。它是由三个部分组成:头部(Header)、载荷(Payload)和签名(Signature)。它的目的是为了在网络应用间传递声明信息,以便在某些情况下对用户进行身份验证和授权。
JWT有以下几个特点:
简洁(Compact):JWT是一个紧凑且自包含的数据格式,它可以通过HTTP头或URL参数进行传输。
自包含(Self-contained):JWT包含了足够的信息,使得客户端可以判断是否信任该令牌,而不需要查询服务器。
可扩展(Extensible):由于JWT是基于JSON格式的,因此可以自定义Payload中的属性来适应各种业务需求。
安全(Secure):JWT使用签名来验证发送方和接收方之间的身份。当使用加密算法时,还可以确保消息的机密性。
2. JWT的三个部分:Header、Payload和Signature
JWT由三个部分组成:头部、载荷和签名。
Header
JWT头部是一个JSON对象,它描述了生成和处理该JWT所需要的算法和类型信息。例如:
{
"alg": "HS256",
"typ": "JWT"
}
其中,"alg"表示签名算法,"typ"表示令牌类型。
Payload
JWT载荷是一个JSON对象,它包含了有关令牌和声明的信息。例如:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
其中,"sub"表示主题(Subject),"name"表示名称,"iat"表示令牌的发行时间(Issued At)。
除了这些声明外,JWT还支持自定义声明。例如:
{
"iss": "http://example.com",
"exp": 1516239922,
"custom_key": "custom_value"
}
其中,"iss"表示颁发者(Issuer),"exp"表示到期时间(Expiration Time),"custom_key"是自定义声明。
Signature
JWT签名用于验证消息的发送方和消息的完整性。签名由头部、载荷和密钥组成,并使用指定的算法进行计算。例如,使用HS256算法可以使用以下方法生成签名:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
其中,"."是分隔符,"secret"是密钥。
3. JWT的工作过程
JWT的工作流程可以描述为以下三个步骤:
用户通过用户名和密码进行身份验证。
服务器对用户进行身份验证,并创建一个JWT令牌,并将其返回给客户端。
客户端在以后的请求中使用该令牌进行身份验证,并在需要访问API时将其发送到服务器。
具体的工作过程如下:
当用户成功登录到Web应用程序时,服务器将根据用户提供的凭据(例如用户名和密码)验证用户的身份,并创建一个JWT令牌。
服务器将从Payload中提取一些信息(例如用户ID或名称)并将其加密到JWT令牌中。
JWT令牌将被加密,并在响应头或URL参数中返回给客户端。
在以后的请求中,客户端将JWT令牌作为授权标头或URL参数发送给服务器。
服务器将使用该令牌进行身份验证,并检查令牌是否被篡改或过期。如果令牌有效,则允许访问所需的API。
4. JWT常见的应用场景和优势
JWT主要应用于Web应用程序和RESTful API,以下是它的一些常见应用场景:
身份验证(Authentication):JWT可以作为身份验证机制,代替传统的Cookie和Session方式。
前后端分离(Front and Backend Separation):在前后端分离的架构中,JWT可以在前端和后端之间进行信任关系的建立和维护。
单点登录(Single Sign On):在多个Web应用程序中共享JWT,可以实现单点登录的效果。
信息交换(Information Exchange):JWT可以用于安全地在不同的系统之间传递信息。
JWT的优势包括:
无状态(Stateless): JWT不需要在服务器上保存用户状态和会话信息,从而简化了服务器的负载和扩展性。
可扩展(Extensible):JWT的载荷可以包含自定义声明,从而满足各种业务场景的需求。
安全(Secure):JWT使用签名来验证发送方和接收方之间的身份。当使用加密算法时,还可以确保消息的机密性。
支持跨域(Cross-origin):JWT可以通过HTTP头或URL参数进行传输,因此支持跨域访问。
5. 如何避免JWT的安全风险
使用JWT时,需要注意以下几点来避免安全风险:
不要将敏感信息存储在JWT中:JWT可以被解密,因此不应该将任何敏感信息存储在JWT中。
对于重要的操作,需要再次进行身份验证:JWT只是一种身份验证机制,而不是授权机制。即使用户已经通过JWT进行了身份验证,服务器仍然需要对他们进行授权和验证。
使用HTTPS协议:由于JWT可能被篡改,因此需要确保使用HTTPS协议以确保消息的机密性和完整性。
设置合理的过期时间:如果JWT永远不会过期,那么它就会成为一个长期有效的访问令牌,从而增加了访问令牌被攻击者盗用的风险。
6.代码案例
JJWT(Java JWT)
JJWT 是一个流行的 Java JWT 库,它提供了一种创建、编码和解码 JWT 的简便方法。以下是一个基于 JJWT 的示例:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtUtils {
private static final long EXPIRATION_TIME = 864_000_000; // 10 days
private static final String SECRET_KEY = "secretKey";
public static String generateToken(String username) {
Date expiryDate = new Date(System.currentTimeMillis() + EXPIRATION_TIME);
return Jwts.builder()
.setSubject(username)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
}
public static String getUsernameFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public static boolean isTokenValid(String token) {
try {
Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
}
在这个示例中,JwtUtils
类包含了生成令牌、从令牌中获取用户名和验证令牌的三个方法。注意,在实际使用中,SECRET_KEY
应该更加复杂。
Spring Security
Spring Security 是一个流行的安全框架,它提供了一种轻松的方式来保护应用程序和 APIs。Spring Security 支持使用 JWT 进行身份验证和授权。以下是一个基于 Spring Security 的示例:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
在这个示例中,SecurityConfig
类扩展了 WebSecurityConfigurerAdapter
类,并覆盖了其中的 configure(HttpSecurity http)
方法。在这个方法中,Spring Security 配置了哪些 URL 公开,哪些需要进行身份验证,并且禁用了 CSRF 保护。配置完后,Spring Security 将 JWT 过滤器添加到 UsernamePasswordAuthenticationFilter
前面。
在实现身份验证之前,需要创建一个用户服务类并实现 UserDetailsService
接口。以下是一个示例:
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(), new ArrayList<>());
}
}
在这个示例中,UserService
类实现了 UserDetailsService
接口,并覆盖其中的 loadUserByUsername(String username)
方法。该方法根据给定的用户名查找用户,如果用户不存在,则抛出 UsernameNotFoundException
异常。
最后,需要实现一个 JWT 过滤器来验证 JWT 并将身份验证信息加载到 Spring Security 的上下文中。以下是一个基于 Spring Security 的 JWT 过滤器示例:
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String TOKEN_PREFIX = "Bearer ";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String token = parseToken(request);
if (token != null && JwtUtils.isTokenValid(token)) {
String username = JwtUtils.getUsernameFromToken(token);
UserDetails userDetails = userService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
} catch (Exception e) {
logger.error("Error while processing authentication request", e);
}
filterChain.doFilter(request, response);
}
private String parseToken(HttpServletRequest request) {
String header = request.getHeader(AUTHORIZATION_HEADER);
if (header != null && header.startsWith(TOKEN_PREFIX)) {
return header.substring(TOKEN_PREFIX.length());
}
return null;
}
}
在这个示例中,JWT 过滤器实现了 OncePerRequestFilter
类,并覆盖了其中的 doFilterInternal
方法。在该方法中,它从请求头中获取 JWT,验证 JWT,将身份验证信息加载到 Spring Security 的上下文中,然后放行请求。
Apache Shiro
Apache Shiro 是一个功能丰富的安全框架,可以用于保护 Java 应用程序和 APIs。Shiro 支持使用 JWT 进行身份验证和授权。以下是一个基于 Shiro 的示例:
@Configuration
public class ShiroConfig {
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm());
return securityManager;
}
@Bean
public JwtRealm realm() {
JwtRealm realm = new JwtRealm();
realm.setCredentialsMatcher(jwtCredentialsMatcher());
return realm;
}
@Bean
public JwtCredentialsMatcher jwtCredentialsMatcher() {
return new JwtCredentialsMatcher();
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
filterFactoryBean.setSecurityManager(securityManager);
filterFactoryBean.setFilters(filters());
filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinition());
return filterFactoryBean;
}
private Map<String, String> filterChainDefinition() {
Map<String, String> filterChainDefinition = new LinkedHashMap<>();
filterChainDefinition.put("/api/auth/**", "anon");
filterChainDefinition.put("/**", "jwt");
return filterChainDefinition;
}
private Map<String, Filter> filters() {
Map<String, Filter> filters = new HashMap<>();
filters.put("jwt", new JwtFilter());
return filters;
}
}
在这个示例中,Shiro 配置了一个名为 securityManager
的安全管理器,其中包含一个名为 realm
的域。realm
类需要实现 org.apache.shiro.realm.Realm
接口,并覆盖其中的 supports(AuthenticationToken token)
和 getAuthenticationInfo(AuthenticationToken token)
方法来验证 JWT。
类似于 Spring Security,需要实现一个 JWT 过滤器来验证 JWT 并将身份验证信息加载到 Shiro 的上下文中。以下是一个基于 Shiro 的 JWT 过滤器示例:
public class JwtFilter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
String token = getToken(request);
if (StringUtils.isNotBlank(token) && JwtUtils.isTokenValid(token)) {
return new JwtToken(token);
}
return null;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
return false;
}
private String getToken(ServletRequest request) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader("Authorization");
if (StringUtils.isNotBlank(token) && token.startsWith("Bearer ")) {
return token.substring(7);
} else {
return null;
}
}
}
在这个示例中,JWT 过滤器扩展了 AuthenticatingFilter
类,并覆盖了其中的 createToken(ServletRequest request, ServletResponse response)
和 onAccessDenied(ServletRequest request, ServletResponse response)
方法。createToken
方法创建一个 JwtToken 对象,并将 JWT 设置为凭据。onAccessDenied
方法在未找到 JWT 时返回 UNAUTHORIZED 状态码。
来源:https://juejin.cn/post/7237009597583474745
猜你喜欢
- 在WinForm程序中,实现TextBox文本输入框占位符的方式也很多,最常用的是方式基于Windows Api SendMessage函数
- 什么是SpringSpring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。 &nbs
- 有时候在单机部署,或者项目没有在IDea 开发工具中运行(idea可以自动打开tomcat项目),需要项目启动后自动打开浏览器访问项目,配置
- 这篇文章主要介绍了springboot多租户设计过程图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的
- @Autowired注解在抽象类中失效最近在工作中遇到这个问题,在抽象类中使用Autowired这个注解,注入mybatis的dao时,总是
- IDEA 初使用昨天,我在某位大神的推荐下,下载了idea编辑器,同时被其强大的功能所震撼。此篇文章去帮助新手小白,来安装并,解决idea安
- Mybatis基础回顾与高级应用数据库:mysql5.7jdk:15引入依赖<!--引入依赖--> &
- 本文实例讲述了Java删除二叉搜索树的任意元素的方法。分享给大家供大家参考,具体如下:一.删除思路分析在删除二叉搜索树的任意元素时,会有三种
- 问题发现今天发生了一件事,令我非常郁闷,就是我在使用一个SDK时,当我调用他的方法时,提示我方法中的参数var1, var2如下:// 方法
- 1.Object类里面常用的方法:protected Object clone()创建并返回此对象的一个副本。boolean equals(
- Java 7的这个新特性改变了警告的对象。构建这些类型毕竟有破坏类型安全的风险,这总得有人知道。但 API 的用户对此是无能为力的,不管do
- 如今APP越来越多,我们每天所使用的的软件也越来越多,可是在我们不付费的情况下,App制造商如何实现,实现收入甚至是盈利呢?答案就是在我们打
- IDEA service层跳转实现类的快捷图标消失了,但别人IDEA同样的代码可以正常看到跳转图标。。(暗示:这只是你的IDEA 编译器的b
- SpringBoot应用启动run方法SpringApplication.java 中执行的代码@SpringBootApplication
- C#的多态性:我的理解是:同一个操作,作用于不同的对象时,会有不同的结果,即同一个方法根据需要,作用于不同的对象时,会有不同的实现。C#的多
- java生成json隐藏关键属性今天在工作中遇到一个这样的问题,当后端返回数据时一些关键信息或敏感信息并不想返回到前端,但是又懒得定义专用的
- 本文实例为大家分享了Java通讯录系统的具体代码,供大家参考,具体内容如下import java.util.Scanner;class Pe
- 使用Eureka实现服务治理作用:实现服务治理(服务注册与发现)简介:Spring Cloud Eureka是Spring Cloud Ne
- 最新对文件的操作比较频繁。这里记录一下常用的几种文件读写的方式。我这里使用窗体来做测试。1:二进制读写/// <summary>
- Java Function的使用一、方法介绍表示接受一个参数并产生结果的函数。参数类型 T - 函数输入的类型R - 函数的结果类型方法介绍