使用SpringBoot开发REST服务
本文介绍如何基于 Spring Boot 搭建一个简易的 REST 服务框架,以及如何通过自定义注解实现 Rest 服务鉴权
搭建框架
pom.xml#
首先,引入相关依赖,数据库使用 mongodb,同时使用 redis 做缓存
注意,这里没有使用tomcat,而是使用undertow
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-starter-test<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">scope</span>></span>test<span class="hljs-tag"></<span class="hljs-name">scope</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-starter-web<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">exclusions</span>></span>
<span class="hljs-tag"><<span class="hljs-name">exclusion</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-starter-tomcat<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">exclusion</span>></span>
<span class="hljs-tag"></<span class="hljs-name">exclusions</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-starter-undertow<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-comment"><!--redis支持--></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-starter-data-redis<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-comment"><!--mongodb支持--></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-starter-data-mongodb<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
- 引入 spring-boot-starter-web 支持 web 服务
- 引入 spring-boot-starter-data-redis 和 spring-boot-starter-data-mongodb 就可以方便的使用 mongodb 和 redis 了
配置文件#
profiles 功能#
为了方便 区分开发环境和线上环境,可以使用 profiles 功能,在 application.properties 里增加
spring.profiles.active=dev
然后增加 application-dev.properties 作为 dev 配置文件。
mondb 配置#
配置数据库地址即可
spring.data.mongodb.uri=mongodb://ip:port/database?readPreference=primaryPreferred
redis 配置#
spring.redis.database=0
# Redis 服务器地址
spring.redis.host=ip
# Redis 服务器连接端口
spring.redis.port=6379
# Redis 服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=0
数据访问#
mongdb#
mongdb 访问很简单,直接定义接口 extends MongoRepository 即可,另外可以支持 JPA 语法,例如:
@Component
public interface UserRepository extends MongoRepository<User, Integer> {
<span class="hljs-keyword">public</span> User <span class="hljs-title function_">findByUserName</span><span class="hljs-params">(String userName)</span>;
}
使用时,加上 @Autowired 注解即可。
@Component
public class AuthService extends BaseService {
<span class="hljs-meta">@Autowired</span>
<span class="hljs-type">UserRepository</span> userRepository;
}
Redis 访问#
使用 StringRedisTemplate 即可直接访问 Redis
@Component
public class BaseService {
@Autowired
protected MongoTemplate mongoTemplate;
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">protected</span> StringRedisTemplate stringRedisTemplate;
}
储存数据:
.stringRedisTemplate.opsForValue().set(token_key, user.getId()+"",token_max_age, TimeUnit.SECONDS);
删除数据:
stringRedisTemplate.delete(getFormatToken(accessToken,platform));
Web 服务#
定义一个 Controller 类,加上 RestController 即可,使用 RequestMapping 用来设置 url route
@RestController
public class AuthController extends BaseController {
<span class="hljs-meta">@RequestMapping</span>(value = {<span class="hljs-string">"/"</span>}, produces = <span class="hljs-string">"application/json;charset=utf-8"</span>, method = {<span class="hljs-type">RequestMethod</span>.<span class="hljs-type">GET</span>, <span class="hljs-type">RequestMethod</span>.<span class="hljs-type">POST</span>})
<span class="hljs-meta">@ResponseBody</span>
public <span class="hljs-type">String</span> main() {
<span class="hljs-keyword">return</span> <span class="hljs-string">"hello world!"</span>;
}
}
现在启动,应该就能看到 hello world!了
服务鉴权
简易 accessToken 机制#
提供登录接口,认证成功后,生成一个 accessToken,以后访问接口时,带上 accessToken,服务端通过 accessToken 来判断是否是合法用户。
为了方便,可以将 accessToken 存入 redis,设定有效期。
String token = EncryptionUtils.sha256Hex(String.format("%s%s", user.getUserName(), System.currentTimeMillis()));
String token_key = getFormatToken(token, platform);
this.stringRedisTemplate.opsForValue().set(token_key, user.getId()+"",token_max_age, TimeUnit.SECONDS);
拦截器身份认证#
为了方便做统一的身份认证,可以基于 Spring 的拦截器机制,创建一个拦截器来做统一认证。
public class AuthCheckInterceptor implements HandlerInterceptor {
}
要使拦截器生效,还需要一步,增加配置:
@Configuration
public class SessionConfiguration extends WebMvcConfigurerAdapter {
<span class="hljs-meta">@Autowired</span>
<span class="hljs-type">AuthCheckInterceptor</span> authCheckInterceptor;
<span class="hljs-meta">@Override</span>
public void addInterceptors(<span class="hljs-type">InterceptorRegistry</span> registry) {
<span class="hljs-keyword">super</span>.addInterceptors(registry);
<span class="hljs-comment">// 添加拦截器</span>
registry.addInterceptor(authCheckInterceptor).addPathPatterns(<span class="hljs-string">"/**"</span>);
}
}
自定义认证注解#
为了精细化权限认证,比如有的接口只能具有特定权限的人才能访问,可以通过自定义注解轻松解决。在自定义的注解里,加上 roles 即可。
/**
* 权限检验注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthCheck {
<span class="hljs-comment">/**
* 角色列表
* @return
*/</span>
<span class="hljs-selector-tag">String</span><span class="hljs-selector-attr">[]</span> <span class="hljs-selector-tag">roles</span>() <span class="hljs-selector-tag">default</span> {};
}
检验逻辑:
- 只要接口加上了 AuthCheck 注解,就必须是登陆用户
- 如果指定了 roles,则除了登录外,用户还应该具备相应的角色。
String[] ignoreUrls = new String[]{
"/user/.*",
"/cat/.*",
"/app/.*",
"/error"
};
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
<span class="hljs-comment">// 0 检验公共参数</span>
<span class="hljs-keyword">if</span>(!checkParams(<span class="hljs-string">"platform"</span>,httpServletRequest,httpServletResponse)){
<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
<span class="hljs-comment">// 1、忽略验证的URL</span>
<span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> httpServletRequest.getRequestURI().toString();
<span class="hljs-keyword">for</span>(String ignoreUrl :ignoreUrls){
<span class="hljs-keyword">if</span>(url.matches(ignoreUrl)){
<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
}
<span class="hljs-comment">// 2、查询验证注解</span>
<span class="hljs-type">HandlerMethod</span> <span class="hljs-variable">handlerMethod</span> <span class="hljs-operator">=</span> (HandlerMethod) handler;
<span class="hljs-type">Method</span> <span class="hljs-variable">method</span> <span class="hljs-operator">=</span> handlerMethod.getMethod();
<span class="hljs-comment">// 查询注解</span>
<span class="hljs-type">AuthCheck</span> <span class="hljs-variable">authCheck</span> <span class="hljs-operator">=</span> method.getAnnotation(AuthCheck.class);
<span class="hljs-keyword">if</span> (authCheck == <span class="hljs-literal">null</span>) {
<span class="hljs-comment">// 无注解,不需要</span>
<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
<span class="hljs-comment">// 3、有注解,先检查accessToken</span>
<span class="hljs-keyword">if</span>(!checkParams(<span class="hljs-string">"accessToken"</span>,httpServletRequest,httpServletResponse)){
<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
<span class="hljs-comment">// 检验token是否过期</span>
<span class="hljs-type">Integer</span> <span class="hljs-variable">userId</span> <span class="hljs-operator">=</span> authService.getUserIdFromToken(httpServletRequest.getParameter(<span class="hljs-string">"accessToken"</span>),
httpServletRequest.getParameter(<span class="hljs-string">"platform"</span>));
<span class="hljs-keyword">if</span>(userId==<span class="hljs-literal">null</span>){
logger.debug(<span class="hljs-string">"accessToken timeout"</span>);
output(ResponseResult.Builder.error(<span class="hljs-string">"accessToken已过期"</span>).build(),httpServletResponse);
<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
<span class="hljs-comment">// 4、再检验是否包含必要的角色</span>
<span class="hljs-keyword">if</span>(authCheck.roles()!=<span class="hljs-literal">null</span>&&authCheck.roles().length><span class="hljs-number">0</span>){
<span class="hljs-type">User</span> <span class="hljs-variable">user</span> <span class="hljs-operator">=</span> authService.getUser(userId);
<span class="hljs-type">boolean</span> <span class="hljs-variable">isMatch</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;
<span class="hljs-keyword">for</span>(String role : authCheck.roles()){
<span class="hljs-keyword">if</span>(user.getRole().getName().equals(role)){
isMatch = <span class="hljs-literal">true</span>;
<span class="hljs-keyword">break</span>;
}
}
<span class="hljs-comment">// 角色未匹配,验证失败</span>
<span class="hljs-keyword">if</span>(!isMatch){
<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
}
<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
服务响应结果封装
增加一个 Builder,方便生成最终结果
public class ResponseResult {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Builder</span>{
ResponseResult responseResult;
Map<String,Object> dataMap = Maps.newHashMap();
<span class="hljs-keyword">public</span> <span class="hljs-title function_">Builder</span><span class="hljs-params">()</span>{
<span class="hljs-built_in">this</span>.responseResult = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ResponseResult</span>();
}
<span class="hljs-keyword">public</span> <span class="hljs-title function_">Builder</span><span class="hljs-params">(String state)</span>{
<span class="hljs-built_in">this</span>.responseResult = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ResponseResult</span>(state);
}
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Builder <span class="hljs-title function_">newBuilder</span><span class="hljs-params">()</span>{
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Builder</span>();
}
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Builder <span class="hljs-title function_">success</span><span class="hljs-params">()</span>{
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Builder</span>(<span class="hljs-string">"success"</span>);
}
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Builder <span class="hljs-title function_">error</span><span class="hljs-params">(String message)</span>{
<span class="hljs-type">Builder</span> <span class="hljs-variable">builder</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Builder</span>(<span class="hljs-string">"error"</span>);
builder.responseResult.setError(message);
<span class="hljs-keyword">return</span> builder;
}
<span class="hljs-keyword">public</span> Builder <span class="hljs-title function_">append</span><span class="hljs-params">(String key,Object data)</span>{
<span class="hljs-built_in">this</span>.dataMap.put(key,data);
<span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
}
<span class="hljs-comment">/**
* 设置列表数据
* <span class="hljs-doctag">@param</span> datas 数据
* <span class="hljs-doctag">@return</span>
*/</span>
<span class="hljs-keyword">public</span> Builder <span class="hljs-title function_">setListData</span><span class="hljs-params">(List<?> datas)</span>{
<span class="hljs-built_in">this</span>.dataMap.put(<span class="hljs-string">"result"</span>,datas);
<span class="hljs-built_in">this</span>.dataMap.put(<span class="hljs-string">"total"</span>,datas.size());
<span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
}
<span class="hljs-keyword">public</span> Builder <span class="hljs-title function_">setData</span><span class="hljs-params">(Object data)</span>{
<span class="hljs-built_in">this</span>.dataMap.clear();
<span class="hljs-built_in">this</span>.responseResult.setData(data);
<span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
}
<span class="hljs-type">boolean</span> <span class="hljs-variable">wrapData</span> <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;
<span class="hljs-comment">/**
* 将数据包裹在data中
* <span class="hljs-doctag">@param</span> wrapData
* <span class="hljs-doctag">@return</span>
*/</span>
<span class="hljs-keyword">public</span> Builder <span class="hljs-title function_">wrap</span><span class="hljs-params">(<span class="hljs-type">boolean</span> wrapData)</span>{
<span class="hljs-built_in">this</span>.wrapData = wrapData;
<span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>;
}
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">build</span><span class="hljs-params">()</span>{
<span class="hljs-type">JSONObject</span> <span class="hljs-variable">jsonObject</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">JSONObject</span>();
jsonObject.put(<span class="hljs-string">"state"</span>,<span class="hljs-built_in">this</span>.responseResult.getState());
<span class="hljs-keyword">if</span>(<span class="hljs-built_in">this</span>.responseResult.getState().equals(<span class="hljs-string">"error"</span>)){
jsonObject.put(<span class="hljs-string">"error"</span>,<span class="hljs-built_in">this</span>.responseResult.getError());
}
<span class="hljs-keyword">if</span>(<span class="hljs-built_in">this</span>.responseResult.getData()!=<span class="hljs-literal">null</span>){
jsonObject.put(<span class="hljs-string">"data"</span>, JSON.toJSON(<span class="hljs-built_in">this</span>.responseResult.getData()));
}<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(dataMap.size()><span class="hljs-number">0</span>){
<span class="hljs-keyword">if</span>(wrapData) {
<span class="hljs-type">JSONObject</span> <span class="hljs-variable">data</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">JSONObject</span>();
dataMap.forEach((key, value) -> {
data.put(key, value);
});
jsonObject.put(<span class="hljs-string">"data"</span>, data);
}<span class="hljs-keyword">else</span>{
dataMap.forEach((key, value) -> {
jsonObject.put(key, value);
});
}
}
<span class="hljs-keyword">return</span> jsonObject.toJSONString();
}
}
<span class="hljs-keyword">private</span> String state;
<span class="hljs-keyword">private</span> Object data;
<span class="hljs-keyword">private</span> String error;
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">getError</span><span class="hljs-params">()</span> {
<span class="hljs-keyword">return</span> error;
}
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setError</span><span class="hljs-params">(String error)</span> {
<span class="hljs-built_in">this</span>.error = error;
}
<span class="hljs-keyword">public</span> <span class="hljs-title function_">ResponseResult</span><span class="hljs-params">()</span>{}
<span class="hljs-keyword">public</span> <span class="hljs-title function_">ResponseResult</span><span class="hljs-params">(String rc)</span>{
<span class="hljs-built_in">this</span>.state = rc;
}
<span class="hljs-comment">/**
* 成功时返回
* <span class="hljs-doctag">@param</span> rc
* <span class="hljs-doctag">@param</span> result
*/</span>
<span class="hljs-keyword">public</span> <span class="hljs-title function_">ResponseResult</span><span class="hljs-params">(String rc, Object result)</span>{
<span class="hljs-built_in">this</span>.state = rc;
<span class="hljs-built_in">this</span>.data = result;
}
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">getState</span><span class="hljs-params">()</span> {
<span class="hljs-keyword">return</span> state;
}
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setState</span><span class="hljs-params">(String state)</span> {
<span class="hljs-built_in">this</span>.state = state;
}
<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">getData</span><span class="hljs-params">()</span> {
<span class="hljs-keyword">return</span> data;
}
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setData</span><span class="hljs-params">(Object data)</span> {
<span class="hljs-built_in">this</span>.data = data;
}
}
调用时可以优雅一点
@RequestMapping(value = {"/user/login","/pc/user/login"}, produces = "application/json;charset=utf-8", method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public String login(String userName,String password,Integer platform) {
User user = this.authService.login(userName,password);
if(user!=null){
// 登陆
String token = authService.updateToken(user,platform);
return ResponseResult.Builder
.success()
.append("accessToken",token)
.append("userId",user.getId())
.build();
}
return ResponseResult.Builder.error("用户不存在或密码错误").build();
}
<span class="hljs-keyword">protected</span> String <span class="hljs-title function_">error</span><span class="hljs-params">(String message)</span>{
<span class="hljs-keyword">return</span> ResponseResult.Builder.error(message).build();
}
<span class="hljs-keyword">protected</span> String <span class="hljs-title function_">success</span><span class="hljs-params">()</span>{
<span class="hljs-keyword">return</span> ResponseResult.Builder
.success()
.build();
}
<span class="hljs-keyword">protected</span> String <span class="hljs-title function_">successDataList</span><span class="hljs-params">(List<?> data)</span>{
<span class="hljs-keyword">return</span> ResponseResult.Builder
.success()
.wrap(<span class="hljs-literal">true</span>) <span class="hljs-comment">// data包裹</span>
.setListData(data)
.build();
}
作者:Jadepeng
出处:jqpeng 的技术记事本 --http://www.cnblogs.com/xiaoqi
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。