RESTful service是一种架构模式,近几年比较流行了,它的轻量级web服务,发挥HTTP协议的原生的GET,PUT,POST,DELETE。 REST模式的Web服务与复杂的SOAP和XML-RPC对比来讲明显的更加简洁,越来越多的web服务开始采用REST风格设计和实现。例如,Amazon.com提供接近REST风格的Web服务进行图书查找;雅虎提供的Web服务也是REST风格的。REST 并非始终是正确的选择。 它作为一种设计 Web 服务的方法而变得流行,这种方法对专有中间件(例如某个应用程序服务器)的依赖比基于 SOAP 和 WSDL 的方法更少。 在某种意义上,通过强调URI和HTTP等早期 Internet 标准,REST 是对大型应用程序服务器时代之前的 Web 方式的回归。
如下图示例:
使用REST的关键是如何抽象资源,抽象得越精确,对REST的应用就越好。
REST服务关键原则:
1. 给一切物体一个ID
2.连接物体在一起
3.使用标准方法
4.资源多重表述
5.无状态通信
本文介绍如何基于Spring Boot搭建一个简易的REST服务框架,以及如何通过自定义注解实现Rest服务鉴权
搭建框架
pom.xml
首先,引入相关依赖,数据库使用mongodb,同时使用redis做缓存
注意:这里没有使用tomcat,而是使用undertow
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<dependency> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<!--redis支持-->
<dependency> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--mongodb支持-->
<dependency> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
|
引入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配置
配置数据库地址即可
1
|
spring.data.mongodb.uri=mongodb: //ip:port/database?readPreference=primaryPreferred
|
redis配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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语法,例如:
1
2
3
4
|
@Component
public interface UserRepository extends MongoRepository<User, Integer> {
public User findByUserName(String userName);
}
|
使用时,加上@Autowired注解即可。
1
2
3
4
5
|
@Component
public class AuthService extends BaseService {
@Autowired
UserRepository userRepository;
}
|
Redis访问
使用StringRedisTemplate即可直接访问Redis
1
2
3
4
5
6
7
8
|
@Component
public class BaseService {
@Autowired
protected MongoTemplate mongoTemplate;
@Autowired
protected StringRedisTemplate stringRedisTemplate;
}
|
储存数据:
1
|
.stringRedisTemplate.opsForValue().set(token_key, user.getId()+ "" ,token_max_age, TimeUnit.SECONDS);
|
删除数据:
1
|
stringRedisTemplate.delete(getFormatToken(accessToken,platform));
|
Web服务
定义一个Controller类,加上RestController即可,使用RequestMapping用来设置url route
1
2
3
4
5
6
7
8
|
@RestController
public class AuthController extends BaseController {
@RequestMapping (value = { "/" }, produces = "application/json;charset=utf-8" , method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public String main() {
return "hello world!" ;
}
}
|
现在启动,应该就能看到hello world!了
服务鉴权
简易accessToken机制
提供登录接口,认证成功后,生成一个accessToken,以后访问接口时,带上accessToken,服务端通过accessToken来判断是否是合法用户。
为了方便,可以将accessToken存入redis,设定有效期。
1
2
3
|
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的拦截器机制,创建一个拦截器来做统一认证。
1
2
|
public class AuthCheckInterceptor implements HandlerInterceptor {
}
|
要使拦截器生效,还需要一步,增加配置:
1
2
3
4
5
6
7
8
9
10
11
|
@Configuration
public class SessionConfiguration extends WebMvcConfigurerAdapter {
@Autowired
AuthCheckInterceptor authCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
super .addInterceptors(registry);
// 添加拦截器
registry.addInterceptor(authCheckInterceptor).addPathPatterns( "/**" );
}
}
|
自定义认证注解
为了精细化权限认证,比如有的接口只能具有特定权限的人才能访问,可以通过自定义注解轻松解决。在自定义的注解里,加上roles即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/**
* 权限检验注解
*/
@Target (ElementType.METHOD)
@Retention (RetentionPolicy.RUNTIME)
@Documented
public @interface AuthCheck {
/**
* 角色列表
* @return
*/
String[] roles() default {};
}
|
检验逻辑:
只要接口加上了AuthCheck注解,就必须是登陆用户
如果指定了roles,则除了登录外,用户还应该具备相应的角色。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
String[] ignoreUrls = new String[]{
"/user/.*" ,
"/cat/.*" ,
"/app/.*" ,
"/error"
};
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
// 0 检验公共参数
if (!checkParams( "platform" ,httpServletRequest,httpServletResponse)){
return false ;
}
// 1、忽略验证的URL
String url = httpServletRequest.getRequestURI().toString();
for (String ignoreUrl :ignoreUrls){
if (url.matches(ignoreUrl)){
return true ;
}
}
// 2、查询验证注解
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 查询注解
AuthCheck authCheck = method.getAnnotation(AuthCheck. class );
if (authCheck == null ) {
// 无注解,不需要
return true ;
}
// 3、有注解,先检查accessToken
if (!checkParams( "accessToken" ,httpServletRequest,httpServletResponse)){
return false ;
}
// 检验token是否过期
Integer userId = authService.getUserIdFromToken(httpServletRequest.getParameter( "accessToken" ),
httpServletRequest.getParameter( "platform" ));
if (userId== null ){
logger.debug( "accessToken timeout" );
output(ResponseResult.Builder.error( "accessToken已过期" ).build(),httpServletResponse);
return false ;
}
// 4、再检验是否包含必要的角色
if (authCheck.roles()!= null &&authCheck.roles().length> 0 ){
User user = authService.getUser(userId);
boolean isMatch = false ;
for (String role : authCheck.roles()){
if (user.getRole().getName().equals(role)){
isMatch = true ;
break ;
}
}
// 角色未匹配,验证失败
if (!isMatch){
return false ;
}
}
return true ;
}
|
服务响应结果封装
增加一个Builder,方便生成最终结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
public class ResponseResult {
public static class Builder{
ResponseResult responseResult;
Map<String,Object> dataMap = Maps.newHashMap();
public Builder(){
this .responseResult = new ResponseResult();
}
public Builder(String state){
this .responseResult = new ResponseResult(state);
}
public static Builder newBuilder(){
return new Builder();
}
public static Builder success(){
return new Builder( "success" );
}
public static Builder error(String message){
Builder builder = new Builder( "error" );
builder.responseResult.setError(message);
return builder;
}
public Builder append(String key,Object data){
this .dataMap.put(key,data);
return this ;
}
/**
* 设置列表数据
* @param datas 数据
* @return
*/
public Builder setListData(List<?> datas){
this .dataMap.put( "result" ,datas);
this .dataMap.put( "total" ,datas.size());
return this ;
}
public Builder setData(Object data){
this .dataMap.clear();
this .responseResult.setData(data);
return this ;
}
boolean wrapData = false ;
/**
* 将数据包裹在data中
* @param wrapData
* @return
*/
public Builder wrap( boolean wrapData){
this .wrapData = wrapData;
return this ;
}
public String build(){
JSONObject jsonObject = new JSONObject();
jsonObject.put( "state" , this .responseResult.getState());
if ( this .responseResult.getState().equals( "error" )){
jsonObject.put( "error" , this .responseResult.getError());
}
if ( this .responseResult.getData()!= null ){
jsonObject.put( "data" , JSON.toJSON( this .responseResult.getData()));
} else if (dataMap.size()> 0 ){
if (wrapData) {
JSONObject data = new JSONObject();
dataMap.forEach((key, value) -> {
data.put(key, value);
});
jsonObject.put( "data" , data);
} else {
dataMap.forEach((key, value) -> {
jsonObject.put(key, value);
});
}
}
return jsonObject.toJSONString();
}
}
private String state;
private Object data;
private String error;
public String getError() {
return error;
}
public void setError(String error) {
this .error = error;
}
public ResponseResult(){}
public ResponseResult(String rc){
this .state = rc;
}
/**
* 成功时返回
* @param rc
* @param result
*/
public ResponseResult(String rc, Object result){
this .state = rc;
this .data = result;
}
public String getState() {
return state;
}
public void setState(String state) {
this .state = state;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this .data = data;
}
}
|
调用时可以优雅一点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
@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();
}
protected String error(String message){
return ResponseResult.Builder.error(message).build();
}
protected String success(){
return ResponseResult.Builder
.success()
.build();
}
protected String successDataList(List<?> data){
return ResponseResult.Builder
.success()
.wrap( true ) // data包裹
.setListData(data)
.build();
}
|
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:http://www.cnblogs.com/xiaoqi/p/SpringBootRest.html