1. 程式人生 > >springmvc跨域+token驗證(app後臺框架搭建二)

springmvc跨域+token驗證(app後臺框架搭建二)

這是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位編碼的。處理速度還是蠻快的。