Java随记
JWT (JSON Web Token) 详解
什么是JWT?
JWT(JSON Web Token)是一种基于JSON的开放标准(RFC 7519)实现的一种令牌,用于在各方之间安全地传输信息。它的结构包括三部分:
- Header:头部,包含令牌类型(JWT)和签名算法(如HMAC SHA256)。
- Payload:负载,包含声明(claims),即需要传递的信息。声明可以分为三类:
- Registered claims:预定义的声明,如
iss
(发行者)、exp
(过期时间)、sub
(主题)、aud
(受众)。 - Public claims:自定义的声明,需要在IANA JSON Web Token Registry中注册或避免冲突。
- Private claims:自定义声明,双方约定使用。
- Registered claims:预定义的声明,如
- Signature:签名,通过将头部和负载进行编码并加上秘密密钥,使用指定的签名算法生成。
JWT 结构示例如下:
Header:
{
"alg": "HS256",
"typ": "JWT"
}
Payload:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Signature:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
JWT示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT 与 Cookie、Token 的对比
Cookie
- 用途:主要用于在客户端存储会话信息。
- 存储位置:通常存储在客户端浏览器中。
- 安全性:容易受到XSS攻击,需要通过
HttpOnly
和Secure
标志增强安全性。 - 生命周期:可以设置为持久性或会话期。
Token
- 用途:通常用于API认证和授权,较通用的术语,JWT是Token的一种具体实现。
- 存储位置:可以存储在浏览器的
localStorage
、sessionStorage
或Cookie中。 - 安全性:取决于Token的类型及其使用方式,JWT可以通过签名和加密提升安全性。
JWT
- 用途:用于在各方之间安全传递信息,尤其适合无状态认证。
- 存储位置:通常存储在浏览器的
localStorage
或Cookie中。 - 安全性:通过签名和可选的加密机制提升安全性,防止篡改和伪造。
- 无状态性:服务器无需存储会话信息,JWT包含了所有认证信息。
Java中的JWT使用示例
以下是使用jjwt
库创建和验证JWT的示例代码:
Maven依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
创建JWT:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtExample {
private static final String SECRET_KEY = "mySecretKey";
public static void main(String[] args) {
String jwt = Jwts.builder()
.setSubject("1234567890")
.setIssuer("example.com")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1 hour
.claim("name", "John Doe")
.claim("admin", true)
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
System.out.println("Generated JWT: " + jwt);
}
}
验证JWT:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
public class JwtExample {
private static final String SECRET_KEY = "mySecretKey";
public static void main(String[] args) {
String jwt = "your.jwt.token.here";
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(jwt)
.getBody();
System.out.println("Subject: " + claims.getSubject());
System.out.println("Issuer: " + claims.getIssuer());
System.out.println("Name: " + claims.get("name"));
System.out.println("Admin: " + claims.get("admin"));
}
}
Python中的JWT使用示例
以下是使用PyJWT
库创建和验证JWT的示例代码:
安装PyJWT:
pip install PyJWT
创建JWT:
import jwt
import datetime
SECRET_KEY = 'mySecretKey'
payload = {
'sub': '1234567890',
'name': 'John Doe',
'admin': True,
'iat': datetime.datetime.utcnow(),
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
print(f'Generated JWT: {token}')
验证JWT:
import jwt
SECRET_KEY = 'mySecretKey'
token = 'your.jwt.token.here'
try:
decoded = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
print(f'Subject: {decoded["sub"]}')
print(f'Name: {decoded["name"]}')
print(f'Admin: {decoded["admin"]}')
except jwt.ExpiredSignatureError:
print('Token has expired')
except jwt.InvalidTokenError:
print('Invalid token')
以上便是对JWT、Cookie和Token的详解及其对比,并给出了Java和Python中的标准使用示例。
@Builder注解(建造者模式)
@Builder注解通常用于实现建造者模式(Builder Pattern),它是Java中的一种设计模式,旨在简化对象的创建过程,特别是在对象的构造函数参数较多时。@Builder
注解是由Lombok库提供的,它通过注解处理器自动生成Builder模式所需的代码,从而减少样板代码。
使用@Builder
注解
基本用法
假设有一个复杂的类Person
,有多个属性需要设置:
import lombok.Builder;
import lombok.ToString;
@Builder
@ToString
public class Person {
private String firstName;
private String lastName;
private int age;
private String address;
}
在这个例子中,Lombok的@Builder
注解会自动生成一个静态的PersonBuilder
类,并在Person
类中添加一个名为builder()
的静态方法,可以用来创建PersonBuilder
实例。@ToString
注解用于方便打印对象内容。
创建对象
使用生成的PersonBuilder
来创建Person
对象:
public class Main {
public static void main(String[] args) {
Person person = Person.builder()
.firstName("John")
.lastName("Doe")
.age(30)
.address("123 Main St")
.build();
System.out.println(person);
}
}
输出:
Person(firstName=John, lastName=Doe, age=30, address=123 Main St)
细节和高级用法
自定义建造者类名称
可以通过@Builder
注解的参数自定义生成的Builder类的名称:
@Builder(builderClassName = "CustomPersonBuilder")
public class Person {
private String firstName;
private String lastName;
private int age;
private String address;
}
设置默认值
可以在字段声明时设置默认值,这些默认值会在构建对象时自动应用:
import lombok.Builder;
import lombok.ToString;
@Builder
@ToString
public class Person {
private String firstName;
private String lastName;
private int age = 18; // Default age
private String address;
}
使用@Singular
处理集合
如果类中包含集合类型的字段,可以使用@Singular
注解来简化集合元素的添加:
import lombok.Builder;
import lombok.Singular;
import lombok.ToString;
import java.util.List;
@Builder
@ToString
public class Person {
private String firstName;
private String lastName;
private int age;
private String address;
@Singular
private List<String> hobbies;
}
public class Main {
public static void main(String[] args) {
Person person = Person.builder()
.firstName("John")
.lastName("Doe")
.age(30)
.address("123 Main St")
.hobby("Reading")
.hobby("Traveling")
.build();
System.out.println(person);
}
}
输出:
Person(firstName=John, lastName=Doe, age=30, address=123 Main St, hobbies=[Reading, Traveling])
总结
@Builder
注解极大地简化了Java中的Builder模式实现,减少了样板代码,使得代码更简洁和易读。通过结合使用Lombok的其他注解(如@ToString
、@Singular
等),可以进一步增强代码的功能性和可维护性。在实际项目中,这样的用法可以提高开发效率并减少人为错误。
SpringSecurity
Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架,专为Java应用程序设计。它是Spring生态系统的一部分,提供了一整套用于保护基于Spring的企业应用程序的安全服务。
Spring Security 的主要功能
身份验证(Authentication):
- 确认用户的身份是否合法。
- 支持多种认证方式,如表单登录、HTTP Basic认证、OAuth、LDAP等。
授权(Authorization):
- 确定已认证的用户是否有权限访问某些资源或执行某些操作。
- 通过角色和权限来控制访问。
防护措施:
- 防止常见的安全攻击,如CSRF(跨站请求伪造)、XSS(跨站脚本攻击)、点击劫持等。
集成:
- 与Spring生态系统的其他组件无缝集成,如Spring MVC、Spring Boot等。
Spring Security 的基本概念
- SecurityContext:包含当前正在执行的线程的安全信息(如认证信息)。
- Authentication:表示用户的认证信息(如用户名、密码、权限等)。
- GrantedAuthority:表示分配给用户的权限。
- UserDetailsService:一个接口,负责从数据库或其他持久化存储中获取用户信息。
- SecurityContextHolder:存储和检索当前执行线程的
SecurityContext
。
Spring Security 的核心组件
配置类:
- 通过继承
WebSecurityConfigurerAdapter
并重写configure
方法,配置HTTP安全和全局安全。
import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user").password("{noop}password").roles("USER") .and() .withUser("admin").password("{noop}admin").roles("ADMIN"); } }
- 通过继承
自定义用户服务:
- 实现
UserDetailsService
接口,提供自定义的用户认证逻辑。
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; @Service public class MyUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 从数据库或其他源获取用户信息 return new org.springframework.security.core.userdetails.User( username, "{noop}password", Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))); } }
- 实现
Spring Security 常见用法
表单登录:
- 默认提供的表单登录机制,通过
/login
处理登录请求。
- 默认提供的表单登录机制,通过
HTTP Basic认证:
- 在配置类中启用 HTTP Basic认证。
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .httpBasic(); }
角色和权限控制:
- 使用
@PreAuthorize
注解在方法级别进行权限控制。
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @Service public class MyService { @PreAuthorize("hasRole('ADMIN')") public void adminMethod() { // 只有ADMIN角色的用户可以访问 } }
- 使用
Spring Security 与 Spring Boot 集成
Spring Boot 提供了对 Spring Security 的自动配置,通过在 pom.xml
或 build.gradle
文件中添加依赖即可轻松引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Spring Boot 自动配置了许多安全设置,例如默认的表单登录和注销功能。可以通过在 application.properties
文件中进行简单配置:
spring.security.user.name=user
spring.security.user.password=password
总结
Spring Security 是一个全面的安全解决方案,能够满足大多数企业级应用的安全需求。它的高可定制性和与Spring生态系统的紧密集成,使得开发人员能够轻松实现复杂的安全机制,并确保应用程序的安全性。
@Autowired、@Resource
在Spring框架中,@Autowired
和@Resource
是常用的依赖注入注解,用于将Spring容器中的bean注入到其他bean中。虽然它们都可以实现依赖注入,但它们的用法和工作机制有所不同。下面详细解释两者的区别。
@Autowired
@Autowired
是Spring框架提供的注解,用于自动装配Spring容器中的bean。它通过类型匹配来自动注入依赖。
使用示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
特点
类型匹配:
@Autowired
通过类型(by type)进行匹配。如果容器中有且只有一个匹配的bean,则注入该bean。可选属性:可以设置
required
属性来控制是否强制注入。如果为false
,则在没有匹配bean时不会抛出异常。@Autowired(required = false) private ServiceB serviceB;
构造函数、字段、方法注入:支持构造函数注入、字段注入和方法注入。
候选者筛选:如果存在多个匹配的bean,可以使用
@Qualifier
注解来指定具体的bean。@Autowired @Qualifier("specificBean") private ServiceB serviceB;
@Resource
@Resource
是Java EE提供的注解,由J2EE规范定义,但Spring也提供了对其的支持。它通过名称(by name)进行匹配,默认情况下会先按名称匹配,再按类型匹配。
使用示例
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
@Component
public class ServiceA {
@Resource(name = "serviceB")
private ServiceB serviceB;
}
特点
- 名称匹配:默认情况下,
@Resource
通过名称(by name)进行匹配。如果没有指定名称属性,则使用字段名称作为bean名称。 - 支持类型匹配:如果按名称没有找到匹配的bean,则会按类型(by type)进行匹配。
- 字段和方法注入:支持字段注入和方法注入,不支持构造函数注入。
- 标准化:
@Resource
是Java规范的一部分,因此在非Spring框架中也可以使用。
区别总结
来源:
@Autowired
:Spring框架提供的注解。@Resource
:Java EE规范提供的注解。
匹配方式:
@Autowired
:默认通过类型匹配,可以结合@Qualifier
按名称匹配。@Resource
:默认通过名称匹配,找不到时再按类型匹配。
配置方式:
@Autowired
:可以指定required
属性。@Resource
:可以指定name
或type
属性,但不能同时指定。
适用范围:
@Autowired
:支持构造函数、字段和方法注入。@Resource
:支持字段和方法注入,不支持构造函数注入。
选择使用
@Autowired
:如果你在Spring环境中工作并且需要通过类型匹配进行自动装配,@Autowired
更为合适,尤其是结合Spring的其他特性(如@Qualifier
)。@Resource
:如果你需要在多个框架中保持一致性(如Java EE环境)或需要按名称匹配bean,@Resource
是一个好的选择。
实际应用中的考虑
在实际开发中,选择@Autowired
还是@Resource
通常取决于项目的具体需求和开发团队的规范。在大多数Spring项目中,@Autowired
使用更为广泛,因为它与Spring生态系统无缝集成,并且提供了更多的灵活性。
@RequestParam、@PathVariable、@RequestBody
在Spring MVC中,@RequestParam
、@PathVariable
和 @RequestBody
注解用于从HTTP请求中获取数据,并将这些数据传递到控制器的方法中。它们各自有不同的用途和使用场景。下面详细介绍它们的区别和用法。
@RequestParam
@RequestParam
用于从请求参数中获取数据(通常是查询参数或表单数据)。它适用于GET和POST请求。
使用示例
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@GetMapping("/greet")
public String greet(@RequestParam(name = "name", required = false, defaultValue = "World") String name) {
return "Hello, " + name;
}
}
在这个示例中,当请求URL为/greet?name=John
时,方法参数name
将被赋值为John
。如果请求中没有提供name
参数,则使用默认值"World"
。
特点
- 绑定查询参数或表单数据:主要用于获取URL中的查询参数或表单数据。
- 可选参数:可以通过
required
属性设置参数是否必须。 - 默认值:可以通过
defaultValue
属性指定参数的默认值。
@PathVariable
@PathVariable
用于从请求URL路径中获取数据。它通常用于RESTful风格的URL。
使用示例
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@GetMapping("/users/{id}")
public String getUser(@PathVariable("id") String userId) {
return "User ID: " + userId;
}
}
在这个示例中,当请求URL为/users/123
时,方法参数userId
将被赋值为123
。
特点
- 绑定URL路径变量:用于从URL路径中提取变量。
- 强类型转换:自动将URL中的路径变量转换为方法参数的类型。
@RequestBody
@RequestBody
用于将请求体中的数据绑定到方法参数。它通常用于处理POST请求中的JSON或XML数据。
使用示例
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@PostMapping("/users")
public String createUser(@RequestBody User user) {
return "User created: " + user.getName();
}
}
class User {
private String name;
private int age;
// Getters and setters
}
在这个示例中,客户端发送的JSON数据将被转换为User
对象,并传递给createUser
方法。
特点
- 绑定请求体数据:将请求体的数据转换为Java对象。
- 适用于复杂数据:常用于处理JSON、XML等复杂的数据格式。
- 自动反序列化:Spring会自动将请求体的内容反序列化为方法参数类型的对象。
区别总结
数据来源:
@RequestParam
:从请求参数(查询参数或表单数据)中获取数据。@PathVariable
:从URL路径变量中获取数据。@RequestBody
:从请求体中获取数据。
使用场景:
@RequestParam
:适用于获取简单的查询参数或表单数据。@PathVariable
:适用于RESTful API中获取路径中的变量。@RequestBody
:适用于处理复杂的请求体数据,如JSON或XML。
数据类型:
@RequestParam
:适用于基本数据类型和字符串。@PathVariable
:适用于基本数据类型和字符串,自动进行类型转换。@RequestBody
:适用于复杂的Java对象,Spring会自动进行反序列化。
实际应用中的考虑
在实际开发中,根据需求选择合适的注解:
- 对于简单的查询参数或表单数据,使用
@RequestParam
。 - 对于RESTful API中的路径变量,使用
@PathVariable
。 - 对于需要从请求体中读取并转换为Java对象的数据,使用
@RequestBody
。
通过合理使用这些注解,可以使Spring MVC控制器方法更加清晰和易于维护。