这是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 newGreeting(counter.incrementAndGet(), String.format(template, name));
}
对全局请求路径的拦截的,则需要在配置类里声明:
@BeanpublicWebMvcConfigurer corsConfigurer() {return newWebMvcConfigurerAdapter() {
@Overridepublic voidaddCorsMappings(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 extendsOncePerRequestFilter {
@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throwsServletException, 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设置过滤:
cors
cn.***.filter.CrossFilter
cors
/*
当然spring4 appalication.xml 也可以配置成:
3)我的配置类配置:
importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.*;importorg.springframework.core.env.Environment;importorg.springframework.web.cors.CorsConfiguration;importorg.springframework.web.cors.UrlBasedCorsConfigurationSource;importorg.springframework.web.filter.CorsFilter;importorg.springframework.web.servlet.HandlerInterceptor;importorg.springframework.web.servlet.ViewResolver;import org.springframework.web.servlet.config.annotation.*;importorg.springframework.web.servlet.mvc.Controller;importorg.springframework.web.servlet.view.InternalResourceViewResolver;importjava.util.ArrayList;importjava.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 extendsWebMvcConfigurerAdapter{private final static Logger logger = LoggerFactory.getLogger(WebConfig.class);publicViewResolver viewResolver() {
InternalResourceViewResolver viewResolver= newInternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/jsp/function/");
viewResolver.setSuffix(".jsp");returnviewResolver;
}//静态文件
@Overridepublic voidaddResourceHandlers(ResourceHandlerRegistry registry) {
logger.info("addResourceHandlers");
registry.addResourceHandler("/static/**").addResourceLocations("/WEB-INF/static/");
}//允许跨域的接口
@Overridepublic voidaddCorsMappings(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
packagecom.ouyang.teson.intercept;importcom.ouyang.teson.WebConfig;importcom.ouyang.teson.util.JwtUtil;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.servlet.HandlerInterceptor;importorg.springframework.web.servlet.ModelAndView;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.io.PrintWriter;/*** Created by ThinkPad on 2017/6/20.*/
public class HeaderTokenInterceptor implementsHandlerInterceptor {private final static Logger logger = LoggerFactory.getLogger(HeaderTokenInterceptor.class);
@Autowired
JwtUtil jwtUtil;
@Overridepublic boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throwsException {//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;
}
@Overridepublic void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throwsException {
}
@Overridepublic void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throwsException {
}//检测到没有token,直接返回不验证
public voiddealErrorReturn(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 类中添加以下两个方法:
@Overridepublic voidaddInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getTokenHeader())
.addPathPatterns("/api/*")
.excludePathPatterns("/robots.txt");
}//token 在header的拦截器
@BeanpublicHandlerInterceptor getTokenHeader(){return newHeaderTokenInterceptor();
}
3) token的实现
token的实现使用jwt组件生成token,如果想要自己通过md5,或者rsa加密生成token也比较简便了,只是这个token要缓存起来,每次进行验证,验证完更新token。更新token主要是更新token里包含的时间,防止token过期。如果使用token的话,可以不用存放缓存,对于登陆验证成功后,我们会生成token,这个token还能带有用户的id等基本信息,我们就可以验证他的过期时间,id等信息。
关于jwt 组件的介绍,可以去看看我的 java组件的jwt的介绍。
直接进入主题了:
maven需要导入
com.auth0
java-jwt
3.2.0
io.jsonwebtoken
jjwt
0.7.0
jjwt 主要是对jwt进一步封装,可以快速开发web的token认证。
jwt工具类:jwtUtil.java
packagecom.ouyang.teson.util;importio.jsonwebtoken.Claims;importio.jsonwebtoken.JwtBuilder;importio.jsonwebtoken.Jwts;importio.jsonwebtoken.SignatureAlgorithm;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.stereotype.Component;importsun.misc.BASE64Decoder;importsun.misc.BASE64Encoder;importjavax.crypto.spec.SecretKeySpec;importjavax.xml.bind.DatatypeConverter;importjava.security.Key;importjava.util.Date;/*** Created by ThinkPad on 2017/6/17.*/@Componentpublic classJwtUtil {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 staticString generToken(String id, String issuer, String subject){long ttlMillis=keeptime;
SignatureAlgorithm signatureAlgorithm=SignatureAlgorithm.HS256;long nowMillis =System.currentTimeMillis();
Date now= newDate(nowMillis);byte[] apiKeySecretBytes =DatatypeConverter.parseBase64Binary(sercetKey);
Key signingKey= newSecretKeySpec(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= newDate(expMillis);
builder.setExpiration(exp);
}returnbuilder.compact();
}publicString updateToken(String token){try{
Claims claims=verifyToken(token);
String id=claims.getId();
String subject=claims.getSubject();
String issuer=claims.getIssuer();
Date date=claims.getExpiration();returngenerToken(id, issuer, subject);
}catch(Exception ex){
ex.printStackTrace();
}return "0";
}publicString updateTokenBase64Code(String token) {
BASE64Encoder base64Encoder=newBASE64Encoder();
BASE64Decoder decoder= newBASE64Decoder();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);returnbase64Encoder.encode(newToken.getBytes());
}catch(Exception ex){
ex.printStackTrace();
}return "0";
}public staticClaims verifyToken(String token){
Claims claims=Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(sercetKey))
.parseClaimsJws(token).getBody();returnclaims;
}
}
关于拦截器的处理token,及更新token,上面已经给出代码,这里不再列出。来看一下简单的控制类,仅供学习,如果要运用到生产环境还得各种配置和测试。
登陆的控制方法:
@RequestMapping("/login")publicString 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后台框架的代码会在第五讲左右,把代码放出来。没有那么充足时间,写博客。