您好,登錄后才能下訂單哦!
這篇文章主要介紹了利用Spring Boot開發REST服務的案例,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
REST服務介紹
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
<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配置
配置數據庫地址即可
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> { public User findByUserName(String userName); }
使用時,加上@Autowired注解即可。
@Component public class AuthService extends BaseService { @Autowired UserRepository userRepository; }
Redis訪問
使用StringRedisTemplate即可直接訪問Redis
@Component public class BaseService { @Autowired protected MongoTemplate mongoTemplate; @Autowired protected 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 { @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,設定有效期。
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 { @Autowired AuthCheckInterceptor authCheckInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { super.addInterceptors(registry); // 添加攔截器 registry.addInterceptor(authCheckInterceptor).addPathPatterns("/**"); } }
自定義認證注解
為了精細化權限認證,比如有的接口只能具有特定權限的人才能訪問,可以通過自定義注解輕松解決。在自定義的注解里,加上roles即可。
/** * 權限檢驗注解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AuthCheck { /** * 角色列表 * @return */ String[] roles() default {}; }
檢驗邏輯:
只要接口加上了AuthCheck注解,就必須是登陸用戶
如果指定了roles,則除了登錄外,用戶還應該具備相應的角色。
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,方便生成最終結果
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; } }
調用時可以優雅一點
@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(); }
感謝你能夠認真閱讀完這篇文章,希望小編分享的“利用Spring Boot開發REST服務的案例”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。