REST service introduction
RESTful service is an architectural model that has become more popular in recent years. Its lightweight web service plays the native GET, PUT, POST, and DELETE of the HTTP protocol. Compared with complex SOAP and XML-RPC, REST mode web services are obviously more concise, and more and more web services are beginning to adopt REST style design and implementation. For example, Amazon.com provides web services that are close to REST style for book searches; the web services provided by Yahoo are also REST style. REST is not always the right choice. It has become popular as a method of designing web services that rely less on proprietary middleware, such as an application server, than on SOAP and WSDL based methods. In a sense, by emphasizing early Internet standards such as URI and HTTP, REST is a regression to the web approach before the era of large application servers.
Example as shown in the following figure:
The key to using REST is how to abstract resources. The more precise the abstraction is, the better the application of REST.
Key principles of REST service:
1. Give all objects an ID
2. Connect objects together
3. Use standard methods
4. Multiple representations of resources
5. Stateless communication
This article introduces how to build a simple REST service framework based on Spring Boot, and how to implement Rest service authentication through custom annotations
Build a framework
pom.xml
First, introduce related dependencies, use mongodb for databases, and use redis for cache
Note: There is no tomcat used here, but 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 support--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--mongodb support--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency>
Introduce spring-boot-starter-web support web services
Introducing spring-boot-starter-data-redis and spring-boot-starter-data-mongodb can easily use mongodb and redis
Configuration File
Profiles function
In order to facilitate the distinction between development environment and online environment, the profiles function can be used to add it in application.properties
spring.profiles.active=dev
Then add application-dev.properties as the dev configuration file.
Mondb configuration
Just configure the database address
spring.data.mongodb.uri=mongodb://ip:port/database?readPreference=primaryPreferred
Redis configuration
spring.redis.database=0 # Redis server address spring.redis.host=ip# Redis server connection port spring.redis.port=6379 # Redis server connection password (default is empty) spring.redis.password=# Maximum number of connections in connection pool (using negative values means no limit) spring.redis.pool.max-active=8 # Maximum blocking waiting time in connection pool (using negative values means no limit) spring.redis.pool.max-wait=-1 # Maximum idle connection in connection pool spring.redis.pool.max-idle=8 # Minimum idle connection in connection pool spring.redis.pool.min-idle=0 # Connection timeout (ms) spring.redis.timeout=0
Data access
mongdb
Access to mongdb is very simple. You can directly define the interface extends MongoRepository. In addition, it can support JPA syntax, for example:
@Componentpublic interface UserRepository extends MongoRepository<User, Integer> { public User findByUserName(String userName);}When using it, just add the @Autowired annotation.
@Componentpublic class AuthService extends BaseService { @Autowired UserRepository userRepository; }Redis access
Use StringRedisTemplate to access Redis directly
@Componentpublic class BaseService { @Autowired protected MongoTemplate mongoTemplate; @Autowired protected StringRedisTemplate stringRedisTemplate; }Store data:
.stringRedisTemplate.opsForValue().set(token_key, user.getId()+"",token_max_age, TimeUnit.SECONDS);
Delete data:
stringRedisTemplate.delete(getFormatToken(accessToken,platform));
Web Services
Define a Controller class, add RestController, and use RequestMapping to set the url route
@RestControllerpublic class AuthController extends BaseController { @RequestMapping(value = {"/"}, produces = "application/json;charset=utf-8", method = {RequestMethod.GET, RequestMethod.POST}) @ResponseBody public String main() { return "hello world!"; }}Now start, you should be able to see hello world! It's
Service authentication
Simple accessToken mechanism
Provide a login interface. After the authentication is successful, an accessToken is generated. When accessing the interface in the future, the accessToken is brought with it. The server uses the accessToken to determine whether it is a legal user.
For convenience, you can save the accessToken into redis and set the validity period.
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);Interceptor identity authentication
In order to facilitate unified identity authentication, an interceptor can be created based on Spring's interceptor mechanism to perform unified authentication.
public class AuthCheckInterceptor implements HandlerInterceptor {}To make the interceptor take effect, one more step is needed to add configuration:
@Configurationpublic class SessionConfiguration extends WebMvcConfigurerAdapter { @Autowired AuthCheckInterceptor authCheckInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { super.addInterceptors(registry); // Add interceptor registry.addInterceptor(authCheckInterceptor).addPathPatterns("/**"); }}Custom authentication annotations
In order to refine permission authentication, for example, some interfaces can only be accessed by people with specific permissions, and can be easily solved through custom annotations. In the custom annotation, just add roles.
/** * Permission verification annotation*/@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface AuthCheck { /** * Role list* @return */ String[] roles() default {};}Inspection logic:
As long as the interface is added with AuthCheck annotation, it must be a logged in user
If roles are specified, in addition to logging in, the user should also have the corresponding role.
String[] ignoreUrls = new String[]{ "/user/.*", "/cat/.*", "/app/.*", "/error" }; public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception { // 0 Verify public parameters if(!checkParams("platform",httpServletRequest,httpServletResponse)){ return false; } // 1. Ignore the verification URL String url = httpServletRequest.getRequestURI().toString(); for(String ignoreUrl :ignoreUrls){ if(url.matches(ignoreUrl)){ return true; } } // 2. Query verification annotation HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); // Query annotation AuthCheck authCheck = method.getAnnotation(AuthCheck.class); if (authCheck == null) { // No annotation, no return true; } // 3. If there is annotation, check accessToken first if(!checkParams("accessToken",httpServletRequest,httpServletResponse)){ return false; } // Check whether the token expires Integer userId = authService.getUserIdFromToken(httpServletRequest.getParameter("accessToken"), httpServletRequest.getParameter("platform")); if(userId==null){ logger.debug("accessToken") timeout"); output(ResponseResult.Builder.error("accessToken expired").build(),httpServletResponse); return false; } // 4. Recheck whether the necessary roles are included 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; } } // The role does not match, verification failed if(!isMatch){ return false; } } return true; } Service response result encapsulation
Add a Builder to facilitate the generation of final results
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; } /** * Set list data* @param datas data* @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; /** * Wrap data in 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; } /** * Return when successful * @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; }} Be more elegant when calling
@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){ // Log in to String token = authService.updateToken(user,platform); return ResponseResult.Builder .success() .append("accessToken",token) .append("userId",user.getId()) .build(); } return ResponseResult.Builder.error("user does not exist or password is wrong").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 package.setListData(data) .build(); }Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.