RESTful API认证方式大致有以下4种:
Basic Authentication
HTTP Basic authentication is described in RFC 2617. It’s a simple username/password scheme. 将用户名与密码进行Base64转码,但这种转码是可逆的。某些爬虫工具可能会获取这些请求信息,直接获取用户的账号和密码,如果采用HTTPS方式发送请求,每次请求和响应会被SSL加密,爬虫无法获取这些信息。另一个问题,由于API通常不能信任用户使用的客户端,如果用户在多个设备(平板、电脑、手机)中登录了这个API服务,其中一个设备出现安全问题,需要修改密码,那么其他设备也需要重新登录才行。为了解决第二个问题,需要对每个设备给予不同的证书。
OAuth
OAuth 是一种授权框架,能够让应用通过HTTP 服务获取有限的访问,访问用户账号信息,例如Facebook, GitHub, DigitalOcean都采用该技术。它可以委托认证服务授权第三方应用访问自己的账号信息。OAuth2 相比OAuth 1,可以在PC端、移动端设备上使用。
OAuth 定义了四种角色:
1)资源所属者,User, 拥有该资源的人,拥有Application所访问资源的权限。
2)客户端, Application, 需要访问用户账号信息的应用。
3)资源服务器, API。
4)授权服务器, API。
Token Authentication
JWT( JSON Web Token), 是一种以Base64编码json对象的token,加密,紧凑且自成一体(self-contained),用于在网络中两个节点之间传递信息。开源标准(RFC 7519)
JWT由三个部分组成:
1)Header, 定义加密算法类型(例如:HS256)、定义类型(JWT)。
2)Payload, 定义token携带的主要信息。
3)Signature, 创建token的签名。
OpenID
OpenID是一个去中心化的网上身份认证系统。对于支持OpenID的网站,用户不需要记住像用户名和密码这样的传统验证标记。取而代之的是,他们只需要预先在一个作为OpenID身份提供者(identity provider, IdP)的网站上注册。OpenID是去中心化的,任何网站都可以使用OpenID来作为用户登录的一种方式,任何网站也都可以作为OpenID身份提供者。OpenID既解决了问题而又不需要依赖于中心性的网站来确认数字身份。
我们使用JWT不仅仅是因为它简单易用,更有着很多优点:
1.简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快。
2.自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库。
3.因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
4.不需要在服务端保存会话信息,特别适用于分布式微服务。
首先要把JWT的maven坐标引入项目的pom文件中:

pom.xml

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.1</version>
        </dependency>


生成token:
TokenService

package com.example.demo.service.impl;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.example.demo.model.User;
import org.springframework.stereotype.Service;

@Service
public class TokenService {

    public String getToken(User user) {
        // token的生成方法
        // Algorithm.HMAC256():使用HS256生成token,密钥则是用户的密码,唯一密钥的话可以保存在服务端。
        // withAudience()存入需要保存在token的信息,这里我把用户ID存入token中。
        String token = "";
        token = JWT.create().withAudience(user.getId().toString()).sign(Algorithm.HMAC256(user.getPassword()));
        return token;
    }
}


接着写个自定义注解,标注在方法上用来进行生成token和验证token等操作:
UserLoginToken

package com.example.demo.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @Target:注解的作用目标
* @Target(ElementType.TYPE)——接口、类、枚举、注解
* @Target(ElementType.FIELD)——字段、枚举的常量
* @Target(ElementType.METHOD)——方法
* @Target(ElementType.PARAMETER)——方法参数
* @Target(ElementType.CONSTRUCTOR) ——构造函数
* @Target(ElementType.LOCAL_VARIABLE)——局部变量
* @Target(ElementType.ANNOTATION_TYPE)——注解
* @Target(ElementType.PACKAGE)——包
* 
* @Retention:注解的保留位置 RetentionPolicy.SOURCE:这种类型的Annotations只在源代码级别保留,编译时就会被忽略,在class字节码文件中不包含。
*                    RetentionPolicy.CLASS:这种类型的Annotations编译时被保留,默认的保留策略,在class文件中存在,但JVM将会忽略,运行时无法获得。
*                    RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。
* @Document:说明该注解将被包含在javadoc中
* @Inherited:说明子类可以继承父类中的该注解
* 
* 
* @author zf
*
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {

   boolean required() default true;
}


然后自定义一个拦截器,拦截所有方法,判断是否拥有上一步的注解,有则生成,验证token:
AuthenticationInterceptor

package com.example.demo.interceptor;

import java.lang.reflect.Method;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import com.example.demo.util.PassToken;
import com.example.demo.util.UserLoginToken;
/**
* 自定义的拦截器
* @author onegis
*
*/
public class AuthenticationInterceptor implements HandlerInterceptor{

   @Autowired
   UserService userService;
   
   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object)
           throws Exception {
        String token = request.getHeader("token");
        // 如果不是映射到方法直接通过
       if(!(object instanceof HandlerMethod)){
           return true;
       }
       HandlerMethod handlerMethod=(HandlerMethod)object;
       Method method = handlerMethod.getMethod();
       
     
       
       //检查有没有需要用户权限的注解 @UserLoginToken
       if(method.isAnnotationPresent(UserLoginToken.class)){
           UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
           if(userLoginToken.required()){
               //开始认证
               if(token==null){
                   throw new RuntimeException("没有token,请重新登录!");
               }
           //拿到token中的userId
               String userId;
               try {
                   userId = JWT.decode(token).getAudience().get(0);
               } catch (Exception e) {
                    throw new RuntimeException("401");
               }
               
               User user = userService.findUserById(Integer.parseInt(userId));
               
               if(user==null){
                   throw new RuntimeException("用户不存在,请重新登录!");
               }
               
               //验证token
                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
               try {
                    jwtVerifier.verify(token);
               } catch (Exception e) {
                   throw new RuntimeException("401");
               }
                return true;
           }
       }
        return true;
   }
}


将自己写的拦截器注入到拦截器配置中:
InterceptorConfig

package com.example.demo.interceptor;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 拦截器配置
* 将自定义的拦截器注入到配置中。
* @author onegis
*
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer  {
   @Override
   public void addInterceptors(InterceptorRegistry registry) {
    //拦截所有请求,判断是否有@UserLoginToken注解,决定是否需要验证。
        registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/*/*");
   }
   @Bean
   public AuthenticationInterceptor authenticationInterceptor(){
       return new AuthenticationInterceptor();
   }
}


最后就是在controller层调用了:
controller

    @RequestMapping("/login")
    @ResponseBody
    public Object login(@RequestBody User user)  {
         JSONObject jsonObject = new JSONObject();
        User userForBase = userService.findUserByUserName(user);
        if (userForBase == null) {
            jsonObject.put("message", "登录失败,用户不存在!");
            return jsonObject;
        } else {
            if (!userForBase.getPassword().equals(user.getPassword())) {
                jsonObject.put("message", "密码错误!");
                return jsonObject;
            } else {
                String token = tokenService.getToken(userForBase);
                jsonObject.put("token", token);
                jsonObject.put("user", userForBase);
                return jsonObject;
            }
        }
    }
    @ResponseBody
    @UserLoginToken
    @RequestMapping("/getMessage")
    public String getMessage() {
        return "通过验证!";
    }

最后修改:2022 年 08 月 17 日
给我一点小钱钱也很高兴啦!o(* ̄▽ ̄*)ブ