前言:提离职了,嗯,这么多年了,真到了提离职的时候,心情真的很复杂。好吧,离职阶段需要把一些项目中的情况说明白讲清楚,这篇博客就简单说一下在平台中对API所做的安全处理(后面讲网关还要说,这里主要讲代码结构)

一、宏观概况

第一点:系统是按照Security规范,通过实现OAuth2.0协议安全控制。

关键词理解:

JWT:JWTJWT 在前后端分离中的应用与实践

规范:Security、JAX-RS(当前选取Jersey:Difference between JAX-RS, Restlet, Jersey, RESTEasy, and Apache CXF
Frameworks

安全协议:OAuth2,参考:理解OAuth 2.0

其他:java自定义注解RBACCONTAINER
REQUEST FILTER

二、实现说明

2.1,安全访问过滤(重要)

在讲调用流程的时候,必须有必要说自定义的安全访问注解,云图平台的伙伴们,如果要理解系统的安全控制,或者仅是为了读接下来的流程说明,这一步很重要,一定要把这部分弄明白:  (这一段是JAX-RS规范很重要的内容)

首先看我们的自定义注解:

package com.dmsdbj.library.app.security;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.ws.rs.NameBinding; @NameBinding
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Secured { String[] value() default {};
}

注意里面的@NameBinding  ,请阅读:Per-JAX-RS
Method Bindings
   必须要明白这个@NameBinding注解是用来干嘛的!!!

再看我们的过滤器:


@Priority(Priorities.AUTHENTICATION)
@Provider
@Secured
public class JWTAuthenticationFilter implements ContainerRequestFilter { @Inject
private Logger log; @Inject
private TokenProvider tokenProvider; @Context
private HttpServletRequest request; @Context
private ResourceInfo resourceInfo; @Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String jwt = resolveToken();
if (StringUtils.isNotBlank(jwt)) {
try {
if (tokenProvider.validateToken(jwt)) {
UserAuthenticationToken authenticationToken = this.tokenProvider.getAuthentication(jwt);
if (!isAllowed(authenticationToken)) {
requestContext.setProperty("auth-failed", true);
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
final SecurityContext securityContext = requestContext.getSecurityContext();
requestContext.setSecurityContext(new SecurityContext() {
@Override
public Principal getUserPrincipal() {
return authenticationToken::getPrincipal;
} @Override
public boolean isUserInRole(String role) {
return securityContext.isUserInRole(role);
} @Override
public boolean isSecure() {
return securityContext.isSecure();
} @Override
public String getAuthenticationScheme() {
return securityContext.getAuthenticationScheme();
}
});
}
} catch (ExpiredJwtException eje) {
log.info("Security exception for user {} - {}", eje.getClaims().getSubject(), eje.getMessage());
requestContext.setProperty("auth-failed", true);
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
} } else {
log.info("No JWT token found");
requestContext.setProperty("auth-failed", true);
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
} } private String resolveToken() {
String bearerToken = request.getHeader(Constants.AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(bearerToken) && bearerToken.startsWith("Bearer ")) {
String jwt = bearerToken.substring(7, bearerToken.length());
return jwt;
}
return null;
} private boolean isAllowed(UserAuthenticationToken authenticationToken) {
Secured secured = resourceInfo.getResourceMethod().getAnnotation(Secured.class);
if (secured == null) {
secured = resourceInfo.getResourceClass().getAnnotation(Secured.class);
}
for (String role : secured.value()) {
if (!authenticationToken.getAuthorities().contains(role)) {
return false;
}
}
return true;
}
}

附:1,You can bind a filter or interceptor to a particular annotation and when that custom annotation is applied, the filter or interceptor will automatically be bound to the annotated JAX-RS method.      (文章:Per-JAX-RS
Method Bindings
 )

2,By default, i.e. if no name binding is applied to the filter implementation class, the filter instance is applied globally, however only after the incoming request has been matched to a particular
resource by JAX-RS runtime. If there is a @NameBinding annotation applied to the filter, the filter will also be executed at the post-match request extension point, but only in case the matched resource or sub-resource method is bound to the same name-binding
annotation. (文章:CONTAINER REQUEST FILTER

简单说来:这个本应该用于所有请求过滤的过滤器,因为加上了@Secure的注解(而@Secure注解又加上了@NameBinding注解),所以,这个过滤器仅被用于有@Secure修饰的特定类、方法!  备注:当前过滤器执行后匹配模式@Provider

2.2,正常访问流程

由上述的过滤器说明,要想请求经过安全限制的API(有@Seured修饰),必须要得到一个可用的token信息(resolveToken方法)。

所以,第一步通过登录获取票据:

服务端:

调用login方法(UserJWTController)

   @Timed
@ApiOperation(value = "authenticate the credential")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "OK")
,
@ApiResponse(code = 401, message = "Unauthorized")})
@Path("/authenticate")
@POST
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public Response login(@Valid LoginDTO loginDTO) throws ServletException { UserAuthenticationToken authenticationToken = new UserAuthenticationToken(loginDTO.getUsername(), loginDTO.getPassword()); try {
User user = userService.authenticate(authenticationToken);
boolean rememberMe = (loginDTO.isRememberMe() == null) ? false : loginDTO.isRememberMe();
String jwt = tokenProvider.createToken(user, rememberMe);
return Response.ok(new JWTToken(jwt)).header(Constants.AUTHORIZATION_HEADER, "Bearer " + jwt).build();
} catch (AuthenticationException exception) {
return Response.status(Status.UNAUTHORIZED).header("AuthenticationException", exception.getLocalizedMessage()).build();
}
}

A:调用了userService.authenticate(authenticationToken),根据当前登录用户,查询用户信息及其角色信息;B:调用tokenProvider.createToken(user, rememberMe),为当前用户生成一个访问票据;C:将当前的票据信息存入到响应header。

客户端:

客户端接收到请求login方法后的Response,会从中提取票据token,并存入localStorage。本系统的具体代码位置:qpp/services/quth/auth.jwt.service  附:HTML
5 Web 存储

API请求:

在第一次登录获取完票据后,后续的请求,当请求的API有自定义注解@Secured时,经过过滤器,首先解析JWT判断是否拥有访问权限,再判断是否允许访问!

附:关键类TokenProvider

package com.dmsdbj.library.app.security.jwt;

import com.dmsdbj.library.app.config.SecurityConfig;
import com.dmsdbj.library.app.security.UserAuthenticationToken;
import com.dmsdbj.library.entity.User;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.slf4j.Logger;
import io.jsonwebtoken.*; public class TokenProvider { @Inject
private Logger log; private static final String AUTHORITIES_KEY = "auth"; private String secretKey; private long tokenValidityInSeconds; private long tokenValidityInSecondsForRememberMe; @Inject
private SecurityConfig securityConfig; @PostConstruct
public void init() {
this.secretKey
= securityConfig.getSecret(); this.tokenValidityInSeconds
= 1000 * securityConfig.getTokenValidityInSeconds();
this.tokenValidityInSecondsForRememberMe
= 1000 * securityConfig.getTokenValidityInSecondsForRememberMe();
} public String createToken(User user, Boolean rememberMe) {
String authorities = user.getAuthorities().stream()
.map(authority -> authority.getName())
.collect(Collectors.joining(",")); long now = (new Date()).getTime();
Date validity;
if (rememberMe) {
validity = new Date(now + this.tokenValidityInSecondsForRememberMe);
} else {
validity = new Date(now + this.tokenValidityInSeconds);
} return Jwts.builder()
.setSubject(user.getLogin())
.claim(AUTHORITIES_KEY, authorities)
.signWith(SignatureAlgorithm.HS512, secretKey)
.setExpiration(validity)
.compact();
} public UserAuthenticationToken getAuthentication(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody(); Set<String> authorities
= Arrays.asList(claims.get(AUTHORITIES_KEY).toString().split(",")).stream()
.collect(Collectors.toSet()); return new UserAuthenticationToken(claims.getSubject(), "", authorities);
} public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
log.info("Invalid JWT signature: " + e.getMessage());
return false;
}
}
}

三、总结

关于本平台的基本安全访问控制,大概就这些内容。其实挺简单的,就是模拟了一个票据生成中心,然后使用了JWT省去了读取服务器端session的步骤,仅通过解析JWT票据进行授权。    嗯,尽可能的在说明白,如果还是不明白的话,小伙伴们及时找我交流(先做任务,不然扛把子该......)

在本项目中涉及到的类:

 

java EE技术体系——CLF平台API开发注意事项(3)——API安全访问控制的更多相关文章

  1. java EE技术体系——CLF平台API开发注意事项(4)——API生命周期治理简单说明

    文档说明 截止日期:20170905,作者:何红霞,联系方式:QQ1028335395.邮箱:hehongxia626@163.com 综述 有幸加入到javaEE技术体系的研究与开发,也得益于大家的 ...

  2. java EE技术体系——CLF平台API开发注意事项(1)——后端开发

    前言:这是一篇帮助小伙伴在本次项目中快速进入到java EE开发的一些说明,为了让同组小伙伴们开发的时候,有个清晰点的思路.昨天给大家演示分享了基本概况,但没有留下文字总结说明,预防后期有人再次问我, ...

  3. java EE技术体系——CLF平台API开发注意事项(2)——后端测试

    前言:上篇博客说到了关于开发中的一些情况,这篇博客主要说明一些关于测试的内容. 一.宏观说明 要求:每一个API都必须经过测试.   备注:如果涉及到服务间调用(如权限和基础数据),而对方服务不可用时 ...

  4. [置顶] 遵循Java EE标准体系的开源GIS服务平台架构

    传送门 ☞ 系统架构设计 ☞ 转载请注明 ☞ http://blog.csdn.net/leverage_1229 传送门 ☞ GoF23种设计模式 ☞ 转载请注明 ☞ http://blog.csd ...

  5. [置顶] 遵循Java EE标准体系的开源GIS服务平台之二:平台部署

    传送门 ☞ 系统架构设计 ☞ 转载请注明 ☞ http://blog.csdn.net/leverage_1229 传送门 ☞ GoF23种设计模式 ☞ 转载请注明 ☞ http://blog.csd ...

  6. Spring 4 官方文档学习 Spring与Java EE技术的集成

    本部分覆盖了以下内容: Chapter 28, Remoting and web services using Spring -- 使用Spring进行远程和web服务 Chapter 29, Ent ...

  7. 揭秘Java架构技术体系

    Web应用,最常见的研发语言是Java和PHP. 后端服务,最常见的研发语言是Java和C/C++. 大数据,最常见的研发语言是Java和Python. 可以说,Java是现阶段中国互联网公司中,覆盖 ...

  8. CTP API开发之一:CTP API简介

    官网下载CTP API 综合交易平台CTP(Comprehensive Transaction Platform)是由上海期货信息技术有限公司(上海期货交易所的全资子公司)开发的期货交易平台,CTP平 ...

  9. zookeeper[3] zookeeper API开发注意事项总结

    如下是根据官方接口文档(http://zookeeper.apache.org/doc/r3.4.1/api/org/apache/zookeeper/ZooKeeper.html#register( ...

随机推荐

  1. JavaScript命名——name不能做变量名

    使用name作为变量名(var name = ‘’),在IE中未引起bug,在Chrome中引起bug但未明确指出命名错误,而是会报其他错误,故不便于发现. 现象原因: javascript中name ...

  2. miniLCD12864 16引脚

    显示图片 main.c #include<reg51.h>#include"st7565.h"//---存一个图片--//unsigned char code pic[ ...

  3. Codeforces 464E #265 (Div. 1) E. The Classic Problem 主席树+Hash

    E. The Classic Problem http://codeforces.com/problemset/problem/464/E 题意:给你一张无向带权图,求S-T的最短路,并输出路径.边权 ...

  4. HDU 5489 Removed Interval 2015 ACM/ICPC Asia Regional Hefei Online (LIS变形)

    定义f[i]表示以i为开头往后的最长上升子序列,d[i]表示以i为结尾的最长上升子序列. 先nlogn算出f[i], 从i-L开始枚举f[i],表示假设i在最终的LIS中,往[0,i-L)里找到满足a ...

  5. Java 可变长参数列表

    Java中定义了变长参数,允许在调用方法时传入不定长度的参数. 定义及调用 在定义方法时,在最后一个形参后加上三点 …,就表示该形参可以接受多个参数值,多个参数值被当成数组传入.上述定义有几个要点需要 ...

  6. Bootstrap历练实例:默认的面板(Panels)

    Bootstrap 面板(Panels) 本章将讲解 Bootstrap 面板(Panels).面板组件用于把 DOM 组件插入到一个盒子中.创建一个基本的面板,只需要向 <div> 元素 ...

  7. (66)zabbix导入/导出配置文件

    通过导入/导出zabbix配置文件,我们可以将自己写好的模板等配置在网络上分享,我们也可以导入网络上分享的配置文件 配置文件有两种格式,分为为xml与json,通过zabbix管理界面可以导出xml, ...

  8. Python爬虫系列-Selenium+Chrome/PhantomJS爬取淘宝美食

    1.搜索关键字 利用Selenium驱动浏览器搜索关键字,得到查询后的商品列表 2.分析页码并翻页 得到商品页码数,模拟翻页,得到后续页面的商品列表 3.分析提取商品内容 利用PyQuery分析源码, ...

  9. python 项目中包中__init__.py文件的作用

    开发python项目时,我遇到了一个这样的现象,当我新建一个pythonpackage时,总会自动地生成一个空的__init__.py文件,因为是python新手,所以很不了解这个空文件的作用是什么, ...

  10. perl-basic-数组操作

    RT...直接看代码 my @stack = ("Fred", "Eileen", "Denise", "Charlie" ...