一、JAX-RS是什么?

JAX-RS,全称为Java API for RESTful Web Services,具体的指支持REST架构风格创建Web服务的规范,具体的实现有Apache CXF、Jersey、RESTEasy等,本文是由的是apache的实现cxf。

二、阅读本片博客你将掌握:

1、如何使用springboot发布jax-rs服务。 2、如何在jax-rs服务中配置拦截器并实现基于token的权限校验功能。

让我们开始把~ 老规矩贴一下pom文件

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">

4.0.0

com.syx

cxf-springboot

1.0-SNAPSHOT

8

8

UTF-8

3.2.4

org.springframework.boot

spring-boot-starter-parent

2.4.4

org.springframework.boot

spring-boot-starter

mysql

mysql-connector-java

8.0.16

com.alibaba

druid

1.0.9

org.springframework.boot

spring-boot-starter-data-jdbc

org.mybatis.spring.boot

mybatis-spring-boot-starter

2.1.4

com.github.pagehelper

pagehelper

5.2.0

org.projectlombok

lombok

true

org.springframework.boot

spring-boot-starter-test

test

org.apache.cxf

cxf-rt-frontend-jaxrs

3.2.4

org.apache.cxf

cxf-rt-transports-http-jetty

3.2.4

com.alibaba

fastjson

1.2.74

io.jsonwebtoken

jjwt

0.9.1

org.springframework.boot

spring-boot-maven-plugin

org.projectlombok

lombok

三、主要程序

1、定义一个用于资源访问的接口(直接使用类也行),同时使用jax-rs的注解进行标注,@PATH代表资源的请求路径,@Produces代表该接口响应的数据类型。在接口声明的方法上,@POST代表以POST类型的请求方式,@Consumes代表该方法所接受的参数类型。同时我们可以和客户端定义好接口方法中参数的json类型。

package com.syx.endpoint;

import javax.ws.rs.Consumes;

import javax.ws.rs.POST;

import javax.ws.rs.Path;

import javax.ws.rs.Produces;

@Path("/syxEndpoint")

@Produces({"application/json"})

public interface SYXEndPoint {

/**

*

* @param args {

* "RuleName":"",

* "Auth": "",

* "RequestParam": {

*

* }

* }

* @return

*/

@POST

@Path("/service")

@Consumes({"application/json"})

String service(String args);

}

2、定义上面声明的资源接口的实现类,由于我们需要使用spring的ioc功能,所以标注上@Compoent注解,将其交给ioc容器管理。

package com.syx.endpoint.impl;

import com.alibaba.fastjson.JSON;

import com.syx.endpoint.SYXEndPoint;

import com.syx.endpoint.handler.Handler;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.ApplicationContext;

import org.springframework.stereotype.Component;

import org.springframework.util.StringUtils;

import java.util.Map;

@Component

public class SYXEndPointImpl implements SYXEndPoint {

@Autowired

private ApplicationContext ctx;

@Override

public String service(String args) {

Map map = JSON.parseObject(args, Map.class);

String ruleName = (String) map.get("RuleName");

Handler handler = (Handler) ctx.getBean(ruleName);

Map requestParam = (Map) map.get("RequestParam");

return handler.service(requestParam).toJsonString();

}

}

3、配置jax-rs服务并发布(由于我们使用的是cxf-rt-transports-http-jetty这个依赖,cxf将使用jetty服务器发布webService服务,当然还有其他的实现,比如netty、tomcat)

package com.syx.config;

import com.syx.endpoint.SYXEndPoint;

import com.syx.endpoint.interceptor.AuthInterceptor;

import org.apache.cxf.bus.spring.SpringBus;

import org.apache.cxf.interceptor.LoggingInInterceptor;

import org.apache.cxf.interceptor.LoggingOutInterceptor;

import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.ImportResource;

import javax.annotation.PostConstruct;

import java.util.Collections;

@Configuration

@ImportResource(value = {"classpath:/META-INF/cxf/cxf.xml"})//加载类路径下的cxf配置文件,用于初始化SpringBus

public class EndpointConfig {

@Autowired

private SpringBus bus;

@Autowired

private SYXEndPoint syxEndPoint;

@PostConstruct

public void init(){

JAXRSServerFactoryBean serverFactoryBean = new JAXRSServerFactoryBean();

//配置服务实现类

serverFactoryBean.setServiceBean(syxEndPoint);

//配置服务的发布地址

serverFactoryBean.setAddress("http://127.0.0.1:9999/cxf/");

//添加请求响应对应的输入输出日志拦截器

serverFactoryBean.setInInterceptors(Collections.singletonList(new LoggingInInterceptor()));

serverFactoryBean.setOutInterceptors(Collections.singletonList(new LoggingOutInterceptor()));

//添加自定义权限控制拦截器

serverFactoryBean.getInInterceptors().add(new AuthInterceptor());

serverFactoryBean.create();

}

}

4、自定义权限拦截器并实现基于token的权限校验功能,这里我们自定一个权限校验类然后继承AbstractPhaseInterceptor同时重写handleMessage和handleFault两个方法,其中handleMessage主要是用于在请求我们的业务方法前进行一个拦截,这里我们配置了一个简单的拦截规则,如果用户请求的是获取token的处理器我们就直接放行,如果是其他的处理器我们的程序就进行token的校验,还有一部分权限资源的控制我没实现,大家可以自行实现(其实就是业务handler和角色的匹配)。handleFault方法主要是用于捕获我们在handleMessage处理中的异常,譬如token过期、token校验不通过等。目前这个自定义的权限拦截器比较粗糙(目前只处理Exception类型的错误,其实这个异常可以更准确 ),大家可以在handleFault中添加更多的异常处理类型。

package com.syx.endpoint.interceptor;

import com.alibaba.fastjson.JSON;

import com.syx.auth.jwt.JwtUtil;

import org.apache.cxf.interceptor.Fault;

import org.apache.cxf.message.Message;

import org.apache.cxf.phase.AbstractPhaseInterceptor;

import org.apache.cxf.phase.Phase;

import org.apache.cxf.transport.http.AbstractHTTPDestination;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

import java.util.List;

import java.util.Map;

/**

* 用于token身份令牌校验

*/

public class AuthInterceptor extends AbstractPhaseInterceptor {

public AuthInterceptor() {

super(Phase.PRE_INVOKE);

}

@Override

public void handleMessage(Message message) throws Fault {

List content = message.getContent(List.class);

String o = (String) content.get(0);

Map map = JSON.parseObject(o, Map.class);

String ruleName = (String) map.get("RuleName");

if("TokenHandler".equals(ruleName)){

return;

}

String token = (String) map.get("Auth");

//token合法性校验

JwtUtil.isExpiration(token);

//解析token中的角色,进行资源使用的权限校验(自行实现。。。)

}

@Override

public void handleFault(Message message) {

Exception ex = message.getContent(Exception.class);

HttpServletResponse resp = (HttpServletResponse)message.getExchange()

.getInMessage().get(AbstractHTTPDestination.HTTP_RESPONSE);

resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

resp.setContentType("text/plain");

try {

resp.getOutputStream().write(ex.getMessage().getBytes());

resp.getOutputStream().flush();

message.getInterceptorChain().setFaultObserver(null); //avoid return soap fault

message.getInterceptorChain().abort();

} catch (IOException e) {

// TODO

}

}

}

好啦~,springboot整合cxf-rt-transports-http-jetty发布jax-rs服务重点内容就讲完了(其实我感觉这个jax-rs和springmvc很像),其他的代码,我将贴在下面。

四、其他程序

1、JwtToken的工具类

package com.syx.auth.jwt;

import com.syx.entity.UserVo;

import io.jsonwebtoken.Claims;

import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

import java.util.List;

public class JwtUtil {

// 主题

private static final String SUBJECT = "zx";

// jwt的token有效期,

private static final long EXPIRITION = 1000L * 60 * 60 * 24 * 7;//7天

// private static final long EXPIRITION = 1000L * 60 * 30; // 半小时

// 加密key(黑客没有该值无法篡改token内容)

private static final String APPSECRET_KEY = "zxdc";

// 用户url权限列表key

private static final String AUTH_CLAIMS = "auth";

/**

* TODO 生成token

*

* @param user

* @return java.lang.String

* @date 2020/7/6 0006 9:26

*/

public static String generateToken(UserVo user) {

String token = Jwts

.builder()

// 主题

.setSubject(SUBJECT)

// 添加jwt自定义值

.claim(AUTH_CLAIMS, user.getRole())

.claim("username", user.getUserName())

.claim("userId", user.getUid())

.setIssuedAt(new Date())

// 过期时间

.setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))

// 加密方式,加密key

.signWith(SignatureAlgorithm.HS256, APPSECRET_KEY).compact();

return token;

}

/**

* 获取用户Id

*

* @param token

* @return

*/

public static String getUserId(String token) {

Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();

return claims.get("userId").toString();

}

/**

* 获取用户名

*

* @param token

* @return

*/

public static String getUsername(String token) {

Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();

return claims.get("username").toString();

}

/**

* 获取用户角色列表, 没有返回空

*

* @param token

* @return

*/

public static List getUserAuth(String token) {

Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();

return (List) claims.get(AUTH_CLAIMS);

}

/**

* 验证是否过期

*

* @param token

* @return

*/

public static boolean isExpiration(String token) {

Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();

System.out.println("过期时间: " + claims.getExpiration());

return claims.getExpiration().before(new Date());

}

}

2、DataSourceConfig数据源配置类

package com.syx.config;

import com.alibaba.druid.pool.DruidDataSource;

import com.github.pagehelper.PageInterceptor;

import org.apache.ibatis.session.SqlSessionFactory;

import org.apache.ibatis.type.JdbcType;

import org.mybatis.spring.SqlSessionFactoryBean;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Primary;

import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

import java.sql.SQLException;

@Configuration

@ConditionalOnClass(DataSource.class)

@EnableTransactionManagement

public class DataSourceConfig {

@Bean

@Primary

public DataSource ds() throws SQLException {

DruidDataSource ds = new DruidDataSource();

ds.setUrl("jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true");

ds.setDriverClassName("com.mysql.cj.jdbc.Driver");

ds.setUsername("root");

ds.setPassword("whh123");

ds.setInitialSize(3);

ds.setMinIdle(3);

ds.setMaxActive(5);

ds.setTestWhileIdle(true);

ds.setValidationQuery("select 1");

ds.init();

return ds;

}

@Bean

@Primary

public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception {

SqlSessionFactoryBean bean = new SqlSessionFactoryBean();

bean.setDataSource(ds);

//配置mybatis的mapper.xml的路径,由于我们使用的是mybatis注解,所以注掉了

// bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResource("classpath:database/**/*.xml"));

org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();

configuration.setUseGeneratedKeys(true);

configuration.setCacheEnabled(true);

configuration.setLazyLoadingEnabled(false);

configuration.setLogPrefix("dao.");

//添加分页插件

configuration.addInterceptor(new PageInterceptor());

configuration.setJdbcTypeForNull(JdbcType.NULL);

bean.setConfiguration(configuration);

return bean.getObject();

}

}

3、CompanyDao

package com.syx.dao;

import com.syx.entity.Company;

import org.apache.ibatis.annotations.Mapper;

import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper

public interface CompanyDao {

@Select("select * from company")

List findAll();

}

4、业务处理的handler,我都粘贴到一起了,大家自行拆开

package com.syx.endpoint.handler;

import com.syx.response.CommonResponse;

import java.util.Map;

public interface Handler {

CommonResponse service(Map args);

}

//-----------------------------------------------------------

package com.syx.endpoint.handler;

import com.github.pagehelper.PageHelper;

import com.github.pagehelper.PageInfo;

import com.syx.dao.CompanyDao;

import com.syx.entity.Company;

import com.syx.response.CommonResponse;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import java.util.List;

import java.util.Map;

@Component("CompanyHandler")

public class CompanyHandler implements Handler {

@Autowired

private CompanyDao companyDao;

@Override

public CommonResponse service(Map args) {

PageHelper.startPage(1,2);

List all = companyDao.findAll();

PageInfo pageInfo = new PageInfo<>(all);

return CommonResponse.success(pageInfo,null);

}

}

//-----------------------------------------------------------

package com.syx.endpoint.handler;

import com.syx.auth.jwt.JwtUtil;

import com.syx.entity.UserVo;

import com.syx.response.CommonResponse;

import com.syx.response.CommonResponseCode;

import org.springframework.stereotype.Component;

import java.util.Arrays;

import java.util.Collections;

import java.util.HashMap;

import java.util.Map;

@Component("TokenHandler")

public class TokenHandler implements Handler {

Map userMap = new HashMap(){

{

UserVo u1 = new UserVo();

u1.setUid(1);

u1.setRole(Arrays.asList("1","2"));

u1.setUserName("张三");

put(u1.getUid(),u1);

}

};

/**

* {

* "RuleName":"TokenHandler",

* "Auth": "",

* "RequestParam": {

* "UserId":"123",

* "Username":"张三"

* }

* }

* @param args

* @return

*/

@Override

public CommonResponse service(Map args) {

int userId = Integer.parseInt((String) args.get("UserId"));

String userName = (String) args.get("Username");

//数据库查询是否有该用户,如果有,生成token,如果没有返回无权访问

UserVo userVo = userMap.get(userId);

if(userVo == null){

return CommonResponse.fail(CommonResponseCode.RC400,"用户"+userId+"不存在");

}

return CommonResponse.success( JwtUtil.generateToken(userVo),null);

}

}

5、实体类

package com.syx.entity;

import lombok.AllArgsConstructor;

import lombok.Getter;

import lombok.Setter;

@Getter

@Setter

@AllArgsConstructor

public class Company {

private int id;

private String name;

private int age;

private String address;

private double salary;

}

//--------------------------------------------

package com.syx.entity;

import lombok.Getter;

import lombok.Setter;

import java.util.List;

@Setter

@Getter

public class UserVo {

/**

* 账户id

*/

private int uid;

/**

* 姓名

*/

private String userName;

/**

* 密码

*/

private String password;

/**

* 是否可用 1可用 0不可用

*/

private Integer lock;

/**

* 角色列表

*/

private List role;

}

6、统一响应的包装类

package com.syx.response;

import com.alibaba.fastjson.JSON;

import org.springframework.util.StringUtils;

public class CommonResponse {

private int status;

private String msg;

private T data;

private long timeStamp;

private CommonResponse() {

this.timeStamp = System.currentTimeMillis();

}

/**

* 成功

* @param data

* @param msg

* @param

* @return

*/

public static CommonResponse success(T data,String msg){

CommonResponse response = new CommonResponse<>();

response.status = CommonResponseCode.RC200.getCode();

if(StringUtils.isEmpty(msg)){

response.msg = CommonResponseCode.RC200.getMsg();

}else{

response.msg = msg;

}

response.data = data;

return response;

}

public static CommonResponse fail(CommonResponseCode code,String msg){

CommonResponse response = new CommonResponse();

response.status = code.getCode();

if(StringUtils.isEmpty(msg)){

response.msg = code.getMsg();

}else{

response.msg = msg;

}

return response;

}

/**

* 转换成json

* @return

*/

public String toJsonString(){

return JSON.toJSONString(this);

}

public int getStatus() {

return status;

}

public String getMsg() {

return msg;

}

public T getData() {

return data;

}

public long getTimeStamp() {

return timeStamp;

}

}

//----------------------------------------

package com.syx.response;

public enum CommonResponseCode {

RC200(200,"SUCCESS"),

RC400(400,"FAIL"),

RC500(500,"Internal ERROR")

;

private final int code;

private final String msg;

CommonResponseCode(int code, String msg) {

this.code = code;

this.msg = msg;

}

public int getCode() {

return code;

}

public String getMsg() {

return msg;

}

}

7、springboot的启动类

package com.syx;

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication

@MapperScan(basePackages = "com.syx.**.dao")

public class MainApp {

public static void main(String[] args) {

SpringApplication.run(MainApp.class);

}

}

精彩文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: