主题
覆盖以下几个内容 :
- jersey 2开rest服务,之前有jersey 1的示例
- jersey 2与spring 4的集成
- jersey 2的http式启动,之前有相关示例
- restful的oauth2.0机制实现,仿微信登录
- jersey的统一异常委托处理,之前有相关介绍
- rest包的DynamicFeature、ContainerRequestFilter等扩展和应用
- jUnit启闭rest http server
- hibernate部分略
目录
文件结构
1.jersey开rest服务/jUnit启闭rest http server
2.容器里的rest服务
3.统一的异常处理
4.oauth2.0实现
文件结构
一、java文件结构
- serv
- ApplicationResConfigure.java 配置rest加载项
- AuthFilter.java rest拦截器
- DynamicRolesDynamicFeature.java 使rest拦截器动态加载
- serv.dao
- AuthDao.java rest服务类-auth2.0相关
- UserDao.java rest服务类-user相关
- serv.exception
- AuthException.java 自定义异常处理类
- MyException.java 自定义异常处理类-根类
- MyExceptionMapper.java 统一异常处理
- ExceptionBean.java 自定义异常VO
- serv.po
- AuthInfo.java oauth授权信息
- User.java 用户信息
- serv.rest
- AuthServ.java restful服务接口类-oauth2.0相关
- UserServ.java restful服务接口类-用户相关
- serv.service
- AuthService.java 服务类-oauth2.0相关
- serv.tools
- code.java 定义返回的json的code字段的值
- CommConst.java 一些常量
- FluzzyMap.java 缓存oauth2.0的code到map(后期应该考虑用其它缓存形式)
二、资源文件结构
- src/main/resources
- spring-context.xml spring的基础配置文件
- webapp
- WEB-INF
- web.xml web工程的配置
- pom.xml maven依赖管理
三、测试相关
- src.test.java
- serv.test
- WebApp.java 以java Main启动http server
- RestTest.java main启动并模拟http请求
1.jersey开rest服务/jUnit启闭rest http server
添加基本的依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.icwant</groupId>
<artifactId>xltx_service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jersey-version>2.6</jersey-version>
<spring-version>4.3.12.RELEASE</spring-version>
<grizzly-version>2.3.10</grizzly-version>
<servlet-api-version>3.0.1</servlet-api-version>
<icwant-version>0.0.1-SNAPSHOT</icwant-version>
</properties>
<dependencies>
<dependency>
<groupId>org.glassfish.grizzly</groupId>
<artifactId>grizzly-http-servlet</artifactId>
<version>${grizzly-version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey-version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet-api-version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-spring3</artifactId>
<version>${jersey-version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.13</version>
</dependency>
<!-- spring-hibernate中间件 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- Spring dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<inherited>true</inherited>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
启动http server:
package serv.test;
import java.io.IOException;
import javax.servlet.ServletRegistration;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.NetworkListener;
import org.glassfish.grizzly.servlet.WebappContext;
import org.glassfish.jersey.servlet.ServletContainer;
public class WebApp {
public static void main(String[] args) throws IOException {
final HttpServer server = new WebApp().startServer();
System.out.println(String.format("Jersey app started with WADL available at "
+ "%sapplication.wadl\nHit enter to stop it...", BASE_URI));
System.in.read();
server.shutdownNow();
}
@Override
public HttpServer startServer() throws IOException {
// Create test web application context.
final WebappContext ctx = new WebappContext("Test Context", "/rest");
//https://github.com/janusdn/jersey2-spring4-grizzly2/blob/master/pom.xml
ctx.addContextInitParameter("contextClass", "org.springframework.web.context.support.XmlWebApplicationContext");
//以下三行可以不配置,默认配置在classpath:applicationContext.xml下
ctx.addContextInitParameter("contextConfigLocation", "classpath*:spring-context.xml");
ctx.addListener("org.springframework.web.context.ContextLoaderListener");
ctx.addListener("org.springframework.web.context.request.RequestContextListener");
//ctx.addFilter("auth", serv.AuthorizationFilter.class);
// Create a servlet registration for the web application in order to wire up Spring managed collaborators to Jersey resources.
ServletRegistration servletRegistration = ctx.addServlet("jersey-servlet", ServletContainer.class);
// The logging filters for server logging.
//servletRegistration.setInitParameter("com.sun.jersey.spi.container.ContainerResponseFilters", "com.sun.jersey.api.container.filter.LoggingFilter");
//servletRegistration.setInitParameter("com.sun.jersey.spi.container.ContainerRequestFilters", "com.sun.jersey.api.container.filter.LoggingFilter");
servletRegistration.setInitParameter("javax.ws.rs.Application", " serv.MyDemoApplication");
//servletRegistration.setInitParameter("com.sun.jersey.config.property.packages", " serv.rest");
//servletRegistration.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
servletRegistration.addMapping("/v1/*");
// final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), false);
HttpServer server = new HttpServer();
NetworkListener listener = new NetworkListener("grizzly2", "localhost", 80);
server.addListener(listener);
ctx.deploy(server);
server.start();
return server;
}
}
加个rest服务接口类即可:
package serv.rest;
import java.util.List;
import javax.annotation.security.DenyAll;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import org.springframework.beans.factory.annotation.Autowired;
import serv.dao.UserDao;
import serv.po.User;
@Path("users")
//注意这个DenyAll标签是为了动态注入filter用,像这种情况必加
@DenyAll
public class UserServ {
@Autowired
private UserDao userDao;
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public User getUser(@PathParam("id") int id) {
return userDao.getUser(id);
}
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<User> getUsers() {
return userDao.getUsers();
}
}
相关的UserDao.java:
package serv.dao;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import dao.BaseDao;
import serv.po.User;
@Component
@Transactional
public class UserDao{
//BaseDao为hibernate的工具类(略)
@Autowired
private BaseDao baseDao;
public User getUser(int userId) {
return baseDao.getFirstByHql("from User where id = ?", userId);
}
public List<User> getUsers() {
return baseDao.getList("from User");
}
}
User.java类如下:
serv.po;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "users")
public class User implements Serializable{
private static final long serialVersionUID = -2392120550365139069L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(name="uname", length=20)
private String uname;
@Column(name="address", length=50)
private String address;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
这时候启动WebApp,访问http://localhost/rest/user/1
即可得到id为1的用户信息,通过以下junit测试类也可以模拟:
serv.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import org.glassfish.grizzly.http.server.HttpServer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import utils.GsonUtil;
public class RestTest{
private HttpServer server;
private WebTarget target;
@Before
public void setUp() throws Exception {
// start the server
server = WebApp.class.newInstance().startServer();
// create the client
Client c = ClientBuilder.newClient();
// uncomment the following line if you want to enable
// support for JSON in the client (you also have to uncomment
// dependency on jersey-media-json module in pom.xml and Main.startServer())
// --
// c.configuration().enable(new org.glassfish.jersey.media.json.JsonJaxbFeature());
target = c.target(IApp.BASE_URI);
}
//关闭http server
@After
public void tearDown() throws Exception {
server.shutdownNow();
}
@Test
public void testGetUser() {
String responseMsg = target.path("/v1/user/1").request().get(String.class);
assertTrue(GsonUtil.isSameJsonStr("{\"id\":1,\"uname\":\"daniel\",\"address\":\"sz\"}", responseMsg));
}
}
2.容器里的rest服务
web.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>xltx_service</display-name>
<!-- 加载spring文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-context.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>jersey-servlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>serv.ApplicationResConfigure</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>jersey-servlet</servlet-name>
<url-pattern>/v1/*</url-pattern>
</servlet-mapping>
</web-app>
启动tomcat即可以访问rest接口
3.统一的异常处理
自定义的几个异常类:
package serv.exception;
import javax.ws.rs.core.Response;
public class ExceptionBean {
protected int code = 0000;
protected String msg = "unkown message";
protected Response.Status status = Response.Status.INTERNAL_SERVER_ERROR;
public ExceptionBean() {
}
public ExceptionBean(Response.Status status, int code, String msg) {
this.code = code;
this.status = status;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Response.Status getStatus() {
return status;
}
public void setStatus(Response.Status status) {
this.status = status;
}
}
自定义异常类:
package serv.exception;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
public class AuthException extends MyException {
private static final long serialVersionUID = -6611852001930903768L;
public AuthException() {
}
public AuthException(String msg) {
this(0, msg);
}
public AuthException(int code, String msg, String... params) {
this(Status.INTERNAL_SERVER_ERROR, code, msg, params);
}
public AuthException(Response.Status status, int code, String msg, String... params) {
super(msg);
this.status = status;
this.code = code;
this.msg = msg;
if (null != params) {
// values = new Object[params.length];
// for (int i = 0; i < params.length; i++) {
// values[i] = params[i];
// }
values = params;
}
}
public AuthException(String msg, Throwable cause, String... params){
this(0, msg, cause, params);
}
public AuthException(int code, String msg, Throwable cause, String... params) {
this(Status.INTERNAL_SERVER_ERROR, code, msg, params);
}
public AuthException(Response.Status status, int code, String msg, Throwable cause, String... params) {
super(msg, cause);
this.status = status;
this.code = code;
this.msg = msg;
if (null != params) {
values = new Object[params.length];
for (int i = 0; i < params.length; i++) {
values[i] = params[i];
}
values = params;
}
}
}
自定义异常父类:
package serv.exception;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
public class MyException extends RuntimeException {
private static final long serialVersionUID = -1322574745106779184L;
protected Object[] values;
protected int code = 0000;
protected String msg = "unkown message";
protected Response.Status status;
public MyException() {
}
public MyException(String msg) {
this(0, msg);
}
public MyException(int code, String msg, String... params) {
this(Status.INTERNAL_SERVER_ERROR, code, msg, params);
}
public MyException(Response.Status status, int code, String msg, String... params) {
super(msg);
this.status = status;
this.code = code;
this.msg = msg;
if (null != params) {
// values = new Object[params.length];
// for (int i = 0; i < params.length; i++) {
// values[i] = params[i];
// }
values = params;
}
}
public MyException(String msg, Throwable cause, String... params){
this(0, msg, cause, params);
}
public MyException(int code, String msg, Throwable cause, String... params) {
this(Status.INTERNAL_SERVER_ERROR, code, msg, params);
}
public MyException(Response.Status status, int code, String msg, Throwable cause, String... params) {
super(msg, cause);
this.status = status;
this.code = code;
this.msg = msg;
if (null != params) {
values = new Object[params.length];
for (int i = 0; i < params.length; i++) {
values[i] = params[i];
}
values = params;
}
}
public Object[] getValues() {
return values;
}
public void setValues(Object[] values) {
this.values = values;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public Response.Status getStatus() {
return status;
}
public ExceptionBean getResource() {
return new ExceptionBean(this.status, this.code, this.msg);
}
}
异常类统一来处理:
package serv.exception;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class MyExceptionMapper implements ExceptionMapper<Exception> {
@Override
public Response toResponse(Exception exception) {
ExceptionBean exResource = new ExceptionBean();
if (exception instanceof MyException) {
exResource = ((MyException)exception).getResource();
} else {
if (exception.getMessage() != null) {
exResource.setMsg(exception.getMessage());
}
}
return Response.ok(exResource).status(exResource.getStatus()).build();
}
}
4.oauth2.0实现
oauth2.0实现一个简化版本,大概的流程如下:
st=>start: Start
op=>operation: Your Operation
cond=>condition: Yes or No?
e=>end
st->op->cond
cond(yes)->e
cond(no)->op
st=>start: start
op1=>operation: 使用appid获取code
op2=>operation: 使用
appid/code/secret
获取access_token
(2小时有效)
/refresh_token
(30天有效)
op3=>operation: 使用appid/access_token访问认证资源
op4=>operation: 返回认证资源
op5=>operation: 使用refresh_token刷新access_token
cond2=>condition: access_token有效?
cond3=>condition: refresh_token有效?
cond_code_ok=>condition: code有效?
e=>end: End
st->op1->op2->cond_code_ok->op3->cond2->op4->e
cond2(yes)->op4
cond2(no)->op5->cond3
cond3(no)->op1
cond_code_ok(yes)->op3
cond_code_ok(no)->op1
建表:
CREATE TABLE `auth` (
`app_id` varchar(18) NOT NULL,
`app_secret` varchar(32) NOT NULL,
`access_token` varchar(88) NOT NULL,
`refresh_token` varchar(88) NOT NULL,
`access_token_expires_in` smallint(6) NOT NULL,
`refresh_token_expires_ltm` bigint(20) NOT NULL, /*失效时间戳*/
`access_token_expires_ltm` bigint(20) NOT NULL,
PRIMARY KEY (`app_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
PO对象类:
package serv.po;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "auth")
public class AuthInfo {
@Id
@Column(name="app_id", length=18)
@GeneratedValue(strategy = GenerationType.IDENTITY)
String appId;
@Column(name="app_secret", length=32)
String appSecret;
@Column(name="access_token", length=88)
String accessToken;
@Column(name="refresh_token", length=88)
String refreshToken;
@Column(name="access_token_expires_in")
int expires_in;
@Column(name="access_token_expires_ltm", length=20)
long accessTokenExpiresLtm;
@Column(name="refresh_token_expires_ltm", length=20)
long refreshTokenExpiresLtm;
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getAppSecret() {
return appSecret;
}
public void setAppSecret(String appSecret) {
this.appSecret = appSecret;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public int getExpires_in() {
return expires_in;
}
public void setExpires_in(int expires_in) {
this.expires_in = expires_in;
}
public long getAccessTokenExpiresLtm() {
return accessTokenExpiresLtm;
}
public void setAccessTokenExpiresLtm(long accessTokenExpiresLtm) {
this.accessTokenExpiresLtm = accessTokenExpiresLtm;
}
public long getRefreshTokenExpiresLtm() {
return refreshTokenExpiresLtm;
}
public void setRefreshTokenExpiresLtm(long refreshTokenExpiresLtm) {
this.refreshTokenExpiresLtm = refreshTokenExpiresLtm;
}
}
接下来有三个类,AuthDao——处理数据表,AuthService——提供数据服务,AuthServ——提供rest接口服务
package serv.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import dao.BaseDao;
import serv.po.AuthInfo;
@Component
@Transactional
public class AuthDao {
@Autowired
private BaseDao baseDao;
public AuthInfo getAuthInfo(String appId) {
return baseDao.getFirstByHql("from AuthInfo where appId=?", appId);
}
public void saveAuthInfo(AuthInfo authInfo) {
baseDao.saveOrUpdate(authInfo);
}
}
package serv.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import dao.AuthDao;
import po.AuthInfo;
@Service
public class AuthService {
@Autowired
private AuthDao authDao;
public AuthInfo getAuthInfo(String appId) {
return authDao.getAuthInfo(appId);
}
public void saveAuthInfo(AuthInfo authInfo) {
authDao.saveAuthInfo(authInfo);
}
}
package serv.rest;
import javax.annotation.security.PermitAll;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.springframework.beans.factory.annotation.Autowired;
import utils.StringUtils;
import serv.exception.AuthException;
import serv.po.AuthInfo;
import serv.service.AuthService;
import serv.tools.CommConst;
import serv.tools.FluzzyMap;
//outl https://github.com/zhangkaitao/shiro-example/blob/master/shiro-example-chapter17-server/src/main/java/com/github/zhangkaitao/shiro/chapter17/web/controller/AccessTokenController.java
@Path("oauth2")
//注意标签PermitAll,用于动态加filter,像这种情况不加
@PermitAll
public class AuthServ {
@Autowired
private AuthService authService;
//根据appid获得code,code可以有多个,不放在数据库中
@GET
@Path("/auth")
@Produces(MediaType.APPLICATION_JSON)
public String auth(@QueryParam("appid") String appId){
if (authService.getAuthInfo(appId) == null) {
return "{\"errmsg\":\"appid required or is error\"}";
}
//返回新code TODO 从缓存拿
String code = StringUtils.getID(CommConst.LEN_CODE);//"0612iyQs0dBnqe13vkPs0zaNQs02iyQz";
FluzzyMap.putData(appId, code);
return new String("{\"code\":\""+ code + "\"}");//len 32
}
@GET
@Path("/access_token")
@Produces(MediaType.APPLICATION_JSON)
public String getAccessToken(@QueryParam("appid") String appId,
@QueryParam("secret") String secret,
@QueryParam("code") String code){
//len 88
//判断code是否有效
String validCode = FluzzyMap.verifyData(appId, code);
if (StringUtils.isEmpty(validCode)) {
return "{\"errcode\":4001, \"errmsg\":\"invalid code or expired\"}";
}
//查看appid是否有效
AuthInfo auInfo = authService.getAuthInfo(appId);
if (auInfo == null) {
throw new AuthException("invalid appid");
}
//生成access_token/refresh_token并返回
String access_token = StringUtils.getID(CommConst.LEN_ACCESS_TOKEN);
String refresh_token = StringUtils.getID(CommConst.LEN_REFRESH_TOKEN);
long current = System.currentTimeMillis() / 1000;
auInfo.setAccessToken(access_token);
auInfo.setRefreshToken(refresh_token);
auInfo.setAccessTokenExpiresLtm(current + 7200);
auInfo.setRefreshTokenExpiresLtm(current + 30*24*3600);
authService.saveAuthInfo(auInfo);
//base对应 /user/*,/address/*等,后续可能有后台相关等
return new String("{\"access_token\":\""+access_token+"\", \"expires_in\":7200,\"refresh_token\":\""+refresh_token+"\",\"scope\":\"snsapi_base\",\"app_id\":\""+appId+"\"}");
}
@GET
@Path("/refresh_token")
@Produces(MediaType.APPLICATION_JSON)
public String refreshAccessToken(@QueryParam("appid") String appid,
@QueryParam("refresh_token") String refresh_token){
//查看appid是否有效
AuthInfo auInfo = authService.getAuthInfo(appid);
if (auInfo == null) {
return "{\"errcode\":4000, \"errmsg\":\"invalid appid\"}";
}
long current = System.currentTimeMillis() / 1000;
if (!refresh_token.equals(auInfo.getRefreshToken())
|| current > auInfo.getRefreshTokenExpiresLtm()){
throw new AuthException("refresh token is not latest or is expires");
}
auInfo.setAccessTokenExpiresLtm(current + 7200);
authService.saveAuthInfo(auInfo);
//base对应 /user/*,/address/*等,后续可能有后台相关等
return new String("{\"access_token\":\""+auInfo.getAccessToken()+"\", \"expires_in\":7200,\"refresh_token\":\""+auInfo.getRefreshToken()+"\",\"scope\":\"snsapi_base\",\"appid\":\""+appid+"\"}");
}
}
引用的其它几类,常量类:
package serv.tools;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
public class CommConst {
public static final int LEN_ACCESS_TOKEN = 88;
public static final int LEN_REFRESH_TOKEN = 88;
public static final int LEN_CODE = 32;
public static final int LEN_APPID = 18;
public static final int LEN_SECRET = 32;
public static Response.Status STATUS_UNAUTHORIZED = Response.Status.UNAUTHORIZED;
}
缓存code的类:
package serv.tools;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class FluzzyMap {
private static Map<String, Set<String>> fluzzyMap = new HashMap<String, Set<String>>();
public static void putData(String k, String v) {
Set<String> lv = fluzzyMap.get(k);
if (lv == null) {
lv = new HashSet<String>();
fluzzyMap.put(k, lv);
}
lv.add(v);
}
//get and remove
public static String verifyData(String k, String v) {
if (fluzzyMap.containsKey(k)) {
Set<String> lv = fluzzyMap.get(k);
if (lv != null && !lv.isEmpty()) {
if (!lv.contains(v)) {
return null;
} else {
lv.remove(v);
return v;
}
}
}
return null;
}
}
有两类情况,如果请求身份验证的相关接口,不应该要求access_token,否则要,见前文的标签@PermitAll、@DenyAll,首先需要一个filter:
package serv;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Priority;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.context.support.XmlWebApplicationContext;
import corelib.utils.StringUtils;
import serv.exception.AuthException;
import serv.po.AuthInfo;
import serv.service.AuthService;
import serv.tools.Code;
import serv.tools.CommConst;
//http://www.jianshu.com/p/a1c2b6c16118
//rest拦截器
public class AuthFilter implements ContainerRequestFilter {
private AuthService authService;
private final List<String> rolesAllowed;
//通过feature实现类(能autowired)进行构造
public AuthFilter(AuthService authService) {
this(authService, null);
}
public AuthFilter(final String... rolesAllowed) {
this(null, Arrays.asList(rolesAllowed));
}
public AuthFilter(AuthService authService, final List<String> rolesAllowed) {
this.authService = authService;
this.rolesAllowed = rolesAllowed;
}
@Override
public void filter(ContainerRequestContext reqContext) throws IOException {
//https://github.com/mestevens/dynamic-role-filter/blob/master/src/main/java/ca/mestevens/java/server/filter/DynamicRoleFilter.java
/*
final MultivaluedMap<String, String> propertyNames = reqContext.getUriInfo().getPathParameters();
final SecurityContext securityContext = reqContext.getSecurityContext();
final boolean authorized = securityContext.getUserPrincipal() != null
&& rolesAllowed.stream()
.map(role -> propertyNames.keySet().stream()
.filter(role::contains)
.reduce(role, (r1, r2) -> r1.replace(String.format("{%s}", r2), propertyNames.getFirst(r2))))
.anyMatch(securityContext::isUserInRole);
*/
final MultivaluedMap<String, String> params = reqContext.getUriInfo().getQueryParameters();
//获取客户端Header中提交的token
String access_token = params.getFirst("access_token");
String app_id = params.getFirst("appid");
verify(app_id, access_token);
}
//https://github.com/mestevens/dynamic-role-filter/blob/master/src/main/java/ca/mestevens/java/server/filter/DynamicRoleFilter.java
//验证token的有效性
private void verify(String app_id, String access_token) {
if (StringUtils.isEmpty(app_id) || StringUtils.isEmpty(access_token)) {
throw new AuthException(CommConst.STATUS_UNAUTHORIZED, Code.NO_AUTH, "appid and access_token required");
}
//无法注入,也无法通过context做文章,痛苦地找解决方案
AuthInfo authInfo = authService.getAuthInfo(app_id);
if (authInfo == null) {
throw new AuthException(CommConst.STATUS_UNAUTHORIZED, Code.NO_AUTH, "appid is invalid");
}
long current = System.currentTimeMillis() / 1000;
if (current > authInfo.getAccessTokenExpiresLtm()) {
throw new AuthException(CommConst.STATUS_UNAUTHORIZED, Code.NO_AUTH, "access_token is expired");
}
}
}
然后还需要动态加载:
package serv;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.container.DynamicFeature;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.FeatureContext;
import org.glassfish.jersey.server.model.AnnotatedMethod;
//https://github.com/mestevens/dynamic-role-filter/blob/master/src/main/java/ca/mestevens/java/server/filter/DynamicRolesDynamicFeature.java
//https://github.com/mestevens/dynamic-role-filter/blob/master/src/test/java/ca/mestevens/java/dropwizard/rest/TestResource.java
import org.springframework.beans.factory.annotation.Autowired;
import serv.service.AuthService;
public class DynamicRolesDynamicFeature implements DynamicFeature {
@Autowired
private AuthService authService;
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext featureContext) {
final AnnotatedMethod method = new AnnotatedMethod(resourceInfo.getResourceMethod());
// 方法加DenyAll标签,必走filter
if (method.isAnnotationPresent(DenyAll.class)) {
featureContext.register(new AuthFilter(authService));
return;
}
final RolesAllowed rolesAllowed = method.getAnnotation(RolesAllowed.class);
// 需要判断权限,根据标签@RolesAllowed("user:{user}:read")定义权限
if (rolesAllowed != null) {
featureContext.register(new AuthFilter(rolesAllowed.value()));
return;
}
if (method.isAnnotationPresent(PermitAll.class)) {
return;
}
// 类?
final Class<?> resourceClass = resourceInfo.getResourceClass();
if (resourceClass.isAnnotationPresent(DenyAll.class)) {
//这也行 https://github.com/alanAraujoSousa/project/blob/06872886d2c08f2b374a36a251ab31205e0cd71f/server/engine/src/main/java/br/com/engine/controller/rest/context/AuthorizationFeature.java
featureContext.register(new AuthFilter(authService));
return;
}
final RolesAllowed classRolesAllowed = resourceClass.getAnnotation(RolesAllowed.class);
if (classRolesAllowed != null) {
featureContext.register(new AuthFilter(classRolesAllowed.value()));
}
}
}