java EE技术体系——CLF平台API开发注意事项(3)——API安全访问控制
来源:互联网 发布:紫竹调网络歌手 编辑:程序博客网 时间:2024/05/29 09:10
前言:提离职了,嗯,这么多年了,真到了提离职的时候,心情真的很复杂。好吧,离职阶段需要把一些项目中的情况说明白讲清楚,这篇博客就简单说一下在平台中对API所做的安全处理(后面讲网关还要说,这里主要讲代码结构)
一、宏观概况
第一点:系统是按照Security规范,通过实现OAuth2.0协议安全控制。
关键词理解:
JWT:JWT,JWT 在前后端分离中的应用与实践
规范:Security、JAX-RS(当前选取Jersey:Difference between JAX-RS, Restlet, Jersey, RESTEasy, and Apache CXF Frameworks)
安全协议:OAuth2,参考:理解OAuth 2.0
其他:java自定义注解,RBAC,CONTAINER 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@Securedpublic 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安全访问控制
- java EE技术体系——CLF平台API开发注意事项(4)——API生命周期治理简单说明
- java EE技术体系——CLF平台API开发注意事项(1)——后端开发
- java EE技术体系——CLF平台API开发注意事项(2)——后端测试
- 基于J2EE规范的中间件——Java EE技术体系
- Java EE----Struts2 Action 访问Servlet API
- java api访问 hbase——demo
- 干货分享—Java EE企业级应用开发技术路线图
- Java技术体系的四大平台(SE ,EE,ME,Card)
- JAVA EE 中文API
- java EE 6API
- TWS API 开发手记——连接到TWS平台
- Java安全应用——Bouncy Castle Crypto API
- Java EE 技术 —— Servlet
- ZooKeeper之Java客户端API使用—权限控制。
- 《Java平台体系》——全书大纲
- 《Java平台体系》——前言
- 《Java平台体系》——前言
- 创建计划任务
- bzoj 3396: [Usaco2009 Jan]Total flow 水流 网络流
- HASH算法
- 自定义组合控件(Android)
- 签名、验签
- java EE技术体系——CLF平台API开发注意事项(3)——API安全访问控制
- 125. Valid Palindrome(回文)
- RSA加密原理
- 计算机编程基础篇笔记 (一)---面对对象和面对过程
- spring cloud 与 docker-compose构建微服务
- 20170904心情总结
- 数字图像处理中的gradient descent (梯度下降算法)应用浅谈
- Vs2013下配置OpenSSL
- 第一篇:Android的基本认识