赞
踩
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文件中
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
我自己写个TokenService,里面只有一个生成token的方法
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等操作
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 onegis * */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface UserLoginToken { boolean required() default true; }
然后自定义一个拦截器,拦截所有方法,判断是否拥有上一步的注解,有则生成,验证token
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; } }
将自己写的拦截器注入到拦截器配置中
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层调用了
@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 "通过验证!"; }
如果直接调用getMessage 接口的话
拦截到没有token,要求重新登录
我们调用Login接口,生成token
接着把token记下来,访问getMessage接口的时候,放到header中
至此结束
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。