什么是注解
注解(Annotation)是Java语言用于将元数据(metadata)与程序元素(类、方法、字段等)关联的一种机制。它提供了一种形式化的方法,使代码可以在编译期被特定的工具或编译器进行处理。注解本身不会直接影响程序的语义,但可以被用来生成辅助代码、校验程序逻辑等。
-注解的作用
-
-
- 编译时检查 - 通过注解表达约束条件,编译器可以检查这些约束是否被违反。例如
@Override
确保方法正确覆写。
- - 生成辅助代码 - 注解可以被工具处理,生成相关的辅助代码,如XML配置文件、代理类等。 -
- 运行时处理 - 在运行时通过反射获取注解信息,并进行相应的处理,如缓存管理、权限控制等。 -
注解的分类
-
-
- Java内置注解 - Java语言内置的标准注解,如
@Override
、@Deprecated
、@SuppressWarnings
等。
- - 元注解 - 用于定义注解的注解,如
@Retention
、@Target
、@Inherited
、@Documented
等。
- - 自定义注解 - 根据具体需求自行定义的注解。 -
元注解详解
元注解是用来定义其他注解的注解,它们可以嵌套地应用于其他注解上。常见的元注解包括:
+认证流程原理
跨域认证的问题
互联网服务离不开用户认证。一般流程是下面这样。
+++1、用户向服务器发送用户名和密码。
+2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
+3、服务器向用户返回一个 session_id,写入用户的 Cookie。
+4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
+5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
+
这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。
+举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?
+一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
+另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。
+JWT (JSON Web Token)
JWT是一种用于在各方之间作为JSON对象安全传输信息的紧凑、URL安全的令牌。它由三部分组成:
-
-
@Retention
- 指定注解的保留策略,可选值为SOURCE(源码级别)、CLASS(class文件级别)和RUNTIME(运行时级别)。
-@Target
- 指定注解可以应用的程序元素类型,如TYPE、METHOD、FIELD等。
-@Inherited
- 指定注解是否可以被子类继承。
-@Documented
- 指定注解是否可以被javadoc工具文档化。
-@Repeatable
- 指定注解是否可以在同一个程序元素上重复使用。(Java 8新增)
+- Header:包含令牌类型(通常为JWT)和签名算法(例如HMAC SHA256)。 +
- Payload:包含声明(claims),即传输的数据。常见的声明包括:用户ID、用户名、过期时间等。 +
- Signature:用于验证令牌的真实性。它是由Header和Payload通过签名算法和密钥生成的。
自定义注解
自定义注解的步骤如下:
--
-
- 使用
@interface
定义注解接口,声明注解的成员变量。
- - 使用元注解对注解进行配置,如
@Retention
、@Target
等。
- - 在代码中使用自定义注解,为其成员变量赋值。 -
- 通过反射获取注解信息,并进行相应的处理。 -
java@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
-public @interface CacheResult {
- String cacheKey() default "";
- long expireTime() default 30 * 60;
-}
+Header.Payload.Signature
-@CacheResult(cacheKey="#userId", expireTime=3600)
-public User getUserInfo(String userId) {
- //...
-}
-
-注解的含义
@Target
@Target元注解用于指定注解可以应用于哪些程序元素。它接受一个ElementType参数的数组,常用的ElementType值包括:
+Header
Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。
++++{ + "alg": "HS256", + "typ": "JWT" +}
上面代码中,alg
属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ
属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT
。
最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。
+Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
++-
-- ElementType.TYPE - 可应用于类、接口、枚举等
-- ElementType.FIELD - 可应用于字段、枚举常量
-- ElementType.METHOD - 可应用于方法
-- ElementType.PARAMETER - 可应用于方法参数
-- ElementType.CONSTRUCTOR - 可应用于构造函数
-- ElementType.LOCAL_VARIABLE - 可应用于局部变量
-- ElementType.ANNOTATION_TYPE - 可应用于注解类型
+- iss (issuer):签发人
+- exp (expiration time):过期时间
+- sub (subject):主题
+- aud (audience):受众
+- nbf (Not Before):生效时间
+- iat (Issued At):签发时间
+- jti (JWT ID):编号
例如,@Target({ElementType.METHOD, ElementType.TYPE})表示该注解可以应用于方法和类/接口上。
-@Retention
@Retention元注解用于指定注解的生命周期,即注解信息在哪个级别被保留下来。它接受一个RetentionPolicy参数,常用的RetentionPolicy值包括:
+
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。
++++{ + "sub": "1234567890", + "name": "John Doe", + "admin": true +}
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
+这个 JSON 对象也要使用 Base64URL 算法转成字符串。
+Signature
Signature 部分是对前两部分的签名,防止数据篡改。
+首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
++++HMACSHA256( + base64UrlEncode(header) + "." + + base64UrlEncode(payload), + secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.
)分隔,就可以返回给用户。
JWT 的使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
+此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization
字段里面。
+++Authorization: Bearer <token>
另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。
+认证流程
-
+
- 用户登录:用户提交用户名和密码。 +
- 认证服务器验证:服务器验证用户信息,成功后生成JWT令牌。 +
- 返回令牌:服务器将JWT令牌返回给客户端。 +
- 客户端存储令牌:客户端存储令牌(通常在本地存储或Cookie中)。 +
- 请求资源:客户端在每次请求资源时,将JWT令牌放入HTTP请求头中。 +
- 服务器验证令牌:服务器验证JWT令牌的有效性,若有效则返回请求的资源。 +
编程步骤
创建SpringBoot项目
使用Spring Initializr创建一个Spring Boot项目,选择以下依赖:
-
-
- RetentionPolicy.SOURCE - 注解只保留在源代码级别,编译时被丢弃 -
- RetentionPolicy.CLASS - 注解保留在class文件级别,但在运行时会被JVM丢弃 -
- RetentionPolicy.RUNTIME - 注解可以在运行时被JVM保留,因此可以通过反射读取注解信息 +
- Spring Web +
- Spring Security +
- Spring Boot DevTools +
- JWT (可以通过Maven或Gradle添加依赖)
@Retention(RetentionPolicy.RUNTIME)表示该注解在运行时可以被反射访问。
-注解的应用场景
-
-
- 配置管理 - 使用注解配置替代XML文件,如Spring的
@Configuration
、@Bean
等。
- - ORM映射 - 使用注解描述对象与数据库表的映射,如JPA的
@Entity
、@Table
等。
- - 校验约束 - 使用注解表达校验规则,如Bean Validation的
@NotNull
、@Size
等。
- - 日志记录 - 使用注解标记需要记录日志的方法,如Log4j的
@Log
。
- - 缓存管理 - 使用注解标记需要缓存的方法及缓存策略。 -
- 安全控制 - 使用注解控制方法的安全访问级别,如Spring Security的
@Secured
。
- - 异步处理 - 使用注解标记需要异步执行的方法,如Spring的
@Async
。
- - 事务管理 - 使用注解配置事务属性,如Spring的
@Transactional
。
-
添加JWT依赖
在pom.xml
中添加JWT依赖:
<dependency>
+ <groupId>io.jsonwebtoken</groupId>
+ <artifactId>jjwt</artifactId>
+ <version>0.9.1</version>
+</dependency>
-
-