這是app后台框架搭建的第二課,主要針對app應用是跨域的運用,講解怎么配置跨域服務;其次講解怎么進行token驗證,通過攔截器設置token驗證和把token設置到http報文中。主要有如下:
1)app后台跨域設置
2)攔截器中設置http報文header中token
3)token的生成實現
====================================================================================================
1,app后台跨域的設置
1.1 springmvc4 有直接在請求映射中對跨域的處理,只需加一個@CrossOrign()
@CrossOrigin(origins = "http://localhost:9000") @GetMapping("/greeting") public Greeting greeting(@RequestParam(required=false, defaultValue="World") String name) { System.out.println("==== in greeting ===="); return new Greeting(counter.incrementAndGet(), String.format(template, name)); }
對全局請求路徑的攔截的,則需要在配置類里聲明:
@Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurerAdapter() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:9000"); } }; }
“/greeting-javaconfig” 則是你定義的請求路徑了,你也可以直接設置為 /api/* 之類的,allowedOrigins也可以匹配成 *
可以參考官方文檔:https://spring.io/guides/gs/rest-service-cors/
1.2 通過filter過濾器進行處理
其實,spring的攔截器也是可以處理跨域的問題,但對於post+json的支持不是很好,用攔截器的支持會好一些:
首先,定義攔截器:
public class CrossFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) { // CORS "pre-flight" request response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); response.addHeader("Access-Control-Allow-Headers", "Content-Type"); response.addHeader("Access-Control-Max-Age", "1800");//30 min } filterChain.doFilter(request, response); } }
其次,在web.xml設置過濾:
<filter> <filter-name>cors</filter-name> <filter-class>cn.***.filter.CrossFilter</filter-class> </filter> <filter-mapping> <filter-name>cors</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
當然spring4 appalication.xml 也可以配置成:
<mvc:cors> <mvc:mapping path="/**" allowed-origins="*" allow-credentials="true" max-age="1800" allowed-methods="GET,POST,OPTIONS"/> </mvc:cors>
3)我的配置類配置:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.*; import org.springframework.core.env.Environment; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.*; import org.springframework.web.servlet.mvc.Controller; import org.springframework.web.servlet.view.InternalResourceViewResolver; import java.util.ArrayList; import java.util.List; /** * Created by ThinkPad on 2017/6/15. */ @Configuration @EnableWebMvc @ComponentScan(basePackages = {"com.ouyang.teson"},useDefaultFilters = true) @PropertySource({"classpath:teson.properties"}) public class WebConfig extends WebMvcConfigurerAdapter{ private final static Logger logger = LoggerFactory.getLogger(WebConfig.class); public ViewResolver viewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/views/jsp/function/"); viewResolver.setSuffix(".jsp"); return viewResolver; } //靜態文件 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { logger.info("addResourceHandlers"); registry.addResourceHandler("/static/**").addResourceLocations("/WEB-INF/static/"); } //允許跨域的接口 @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/*").allowedOrigins("*") .allowCredentials(false) .allowedMethods("GET", "POST", "DELETE", "PUT") .allowedHeaders("Access-Control-Allow-Origin","Access-Control-Allow-Headers","Access-Control-Allow-Methods" ,"Access-Control-Max-Age")
.exposedHeaders("Access-Control-Allow-Origin")
.maxAge(3600);
}
}
2) 在攔截器中設置token
在攔截器中設置token這個比較簡單,我就直接帶過了,看配置:
攔截器類:HeaderTokenInterceptor.java
package com.ouyang.teson.intercept; import com.ouyang.teson.WebConfig; import com.ouyang.teson.util.JwtUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * Created by ThinkPad on 2017/6/20. */ public class HeaderTokenInterceptor implements HandlerInterceptor { private final static Logger logger = LoggerFactory.getLogger(HeaderTokenInterceptor.class); @Autowired JwtUtil jwtUtil; @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { // String contentPath=httpServletRequest.getContextPath(); // System.out.println("contenxPath:"+contentPath); String requestURI=httpServletRequest.getRequestURI(); String tokenStr=httpServletRequest.getParameter("token"); String token=""; if(requestURI.contains("/api/")){ token=httpServletRequest.getHeader("token"); if(token==null && tokenStr==null){ System.out.println("real token:======================is null"); String str="{'errorCode':801,'message':'缺少token,無法驗證','data':null}"; dealErrorReturn(httpServletRequest,httpServletResponse,str); return false; } if(tokenStr!=null){ token=tokenStr; } token=jwtUtil.updateToken(token); System.out.println("real token:=============================="+token); System.out.println("real ohter:=============================="+httpServletRequest.getHeader("Cookie")); } httpServletResponse.setHeader("token",token); /* httpServletResponse.setHeader("Access-Control-Allow-Origin", "*"); httpServletResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT");*/ return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { } // 檢測到沒有token,直接返回不驗證 public void dealErrorReturn(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,Object obj){ String json = (String)obj; PrintWriter writer = null; httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setContentType("text/html; charset=utf-8"); try { writer = httpServletResponse.getWriter(); writer.print(json); } catch (IOException ex) { logger.error("response error",ex); } finally { if (writer != null) writer.close(); } } }
httpServletResponse.setHeader("token",token)是設置返回response的header的token信息,每一次攔截的時候,會查看是否有token,如果沒有就直接報錯

關於app 后台返回的結果,在實際的開發中需要統一返回數據格式,這個會在下一節中講到。
在webconfig.java 類中添加以下兩個方法:
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(getTokenHeader()) .addPathPatterns("/api/*") .excludePathPatterns( "/robots.txt"); } //token 在header的攔截器 @Bean public HandlerInterceptor getTokenHeader(){ return new HeaderTokenInterceptor(); }
3) token的實現
token的實現使用jwt組件生成token,如果想要自己通過md5,或者rsa加密生成token也比較簡便了,只是這個token要緩存起來,每次進行驗證,驗證完更新token。更新token主要是更新token里包含的時間,防止token過期。如果使用token的話,可以不用存放緩存,對於登陸驗證成功后,我們會生成token,這個token還能帶有用戶的id等基本信息,我們就可以驗證他的過期時間,id等信息。
關於jwt 組件的介紹,可以去看看我的 java組件的jwt的介紹。
直接進入主題了:
maven需要導入
<!-- java-web-token 驗證授權--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.2.0</version> </dependency> <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency>
jjwt 主要是對jwt進一步封裝,可以快速開發web的token認證。
jwt工具類:jwtUtil.java
package com.ouyang.teson.util; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; import java.security.Key; import java.util.Date; /** * Created by ThinkPad on 2017/6/17. */ @Component public class JwtUtil { public static String sercetKey="mingtianhenganghao"; public final static long keeptime=1800000; /* @Value("${token.sercetKey}") public static String sercetKey; @Value("${token.keeptime:30000}") public static long keeptime;*/ public static String generToken(String id, String issuer, String subject){ long ttlMillis=keeptime; SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(sercetKey); Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); JwtBuilder builder = Jwts.builder().setId(id) .setIssuedAt(now); if(subject!=null){ builder.setSubject(subject); } if(issuer!=null){ builder.setIssuer(issuer); } builder .signWith(signatureAlgorithm, signingKey); if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); builder.setExpiration(exp); } return builder.compact(); } public String updateToken(String token){ try { Claims claims=verifyToken(token); String id=claims.getId(); String subject=claims.getSubject(); String issuer=claims.getIssuer(); Date date = claims.getExpiration(); return generToken(id, issuer, subject); }catch (Exception ex){ ex.printStackTrace(); } return "0"; } public String updateTokenBase64Code(String token) { BASE64Encoder base64Encoder=new BASE64Encoder(); BASE64Decoder decoder = new BASE64Decoder(); try { token=new String(decoder.decodeBuffer(token),"utf-8"); Claims claims=verifyToken(token); String id=claims.getId(); String subject=claims.getSubject(); String issuer=claims.getIssuer(); Date date = claims.getExpiration(); String newToken = generToken(id, issuer, subject); return base64Encoder.encode(newToken.getBytes()); }catch (Exception ex){ ex.printStackTrace(); } return "0"; } public static Claims verifyToken(String token){ Claims claims = Jwts.parser() .setSigningKey(DatatypeConverter.parseBase64Binary(sercetKey)) .parseClaimsJws(token).getBody(); return claims; } }
關於攔截器的處理token,及更新token,上面已經給出代碼,這里不再列出。來看一下簡單的控制類,僅供學習,如果要運用到生產環境還得各種配置和測試。
登陸的控制方法:
@RequestMapping("/login") public String login(String name,String password, Model model){ if(name==null || password==null){ return "error"; } String token = jwtUtil.generToken("xiaoming",null,null); model.addAttribute("token", token); return "redirect:/api/liu"; }
這里沒有做驗證,只是簡單根據賬戶密碼,生成token后,重定向;接下來的任務就交給攔截器了,攔截器會攔截/api/* 下的請求,然后請求參數有token的會驗證token,並更新token,並把token放到header里。
這里可以看到token字符串有兩個點,最好把jwt生成的token進行base64位編碼,jwtUtil.java里有updateTokenBase64Code(String token)就是處理token進行base64位編碼的。處理速度還是蠻快的。
最后,app后台框架的代碼會在第五講左右,把代碼放出來。沒有那么充足時間,寫博客。