隨着業務的需求普通的springmvc+jsp已經不能知足咱們的系統了,會逐漸把後臺和前端展現分離開來,下面咱們就來把普通的springmvc+jsp分爲 springmvc只提供rest接口,前端用ajax請求接口渲染到html中。javascript
後臺提供接口是一個tomcat服務器html
前臺訪問數據是nginx訪問rest接口前端
可是有一個問題 ,發現沒有。就是兩個是不一樣的域名,因此存在跨域,下面我會把一些關鍵的代碼貼出來。java
首先解決接口訪問跨域的問題。jquery
自定義一個攔截請求的Filternginx
/** * post 跨域攔截 * @Project: children-watch-web-api * @Class JsonpPostFilter * @Description: TODO * @author cd 14163548@qq.com * @date 2018年1月10日 下午4:12:11 * @version V1.0 */ @Component public class JsonpPostFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest,ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) servletResponse; //String origin = (String) servletRequest.getRemoteHost() + ":"+ servletRequest.getRemotePort(); //構造頭部信息 response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods","POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers","x-requested-with,Authorization,X-Token"); response.setHeader("Access-Control-Allow-Credentials", "true"); filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { } }
而後再配置web.xmlweb
<!-- 跨域配置--> <filter> <filter-name>cors</filter-name> <filter-class>com.axq.watch.web.api.config.JsonpPostFilter</filter-class> </filter> <filter-mapping> <filter-name>cors</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
這樣就能夠實現跨域訪問了。ajax
接下來就是登錄的問題,redis
思路:spring
1.用戶輸入帳號密碼,到後臺查詢,正確返回服務器生成的token,錯誤返回相應的錯誤信息。
2.用戶拿到token保存到本地cookie.
3.用戶要調用相應的接口須要把token傳入頭部。
4.後臺獲取訪問的接口,看頭部是否有token,在比對是否過時。
實現代碼
token接口
/** * REST 鑑權 * @Project: children-watch-api * @Class TokenService * @Description: 登陸用戶的身份鑑權 * @author cd 14163548@qq.com * @date 2018年1月24日 上午11:43:28 * @version V1.0 */ public interface TokenLoginService { String createToken(String openid); boolean checkToken(String token); String getOpenId(String token); void deleteToken(String token); }
token接口登錄實現
/** * * @Project: children-watch-service * @Class TokenServiceImpl * @Description: 登陸用戶的身份鑑權 的實現 這裏存入redis * @author cd 14163548@qq.com * @date 2018年1月24日 上午11:47:23 * @version V1.0 */ @Service("tokenLoginService") public class TokenLoginServiceImpl implements TokenLoginService { @Autowired private RedisCache redisCache; /** * 利用UUID建立Token(用戶登陸時,建立Token) */ @Override public String createToken(String openid) { String token = RandomString.createUUID().toUpperCase(); redisCache.set(token, openid); redisCache.expire(token, TokenConstant.TOKEN_EXPIRES_HOUR); return token; } @Override public boolean checkToken(String token) { return StringUtils.isNotBlank(token) && redisCache.hasKey(token); } @Override public void deleteToken(String token) { redisCache.del(token); } @Override public String getOpenId(String token) { if(checkToken(token)){ return (String) redisCache.get(token); } return ""; }
這裏我是存入redis中的,方便集羣
自定義一個註解,標識是否忽略REST安全性檢查
/** * @Project: children-watch-web-api * @Class IgnoreSecurity 自定義註解 * @Description: 標識是否忽略REST安全性檢查 * @author cd 14163548@qq.com * @date 2018年1月24日 下午12:13:21 * @version V1.0 */ @Target(ElementType.METHOD) //指明該類型的註解能夠註解的程序元素的範圍 @Retention(RetentionPolicy.RUNTIME) //指明瞭該Annotation被保留的時間長短 @Documented //指明擁有這個註解的元素能夠被javadoc此類的工具文檔化 public @interface IgnoreSecurity { }
自定義異常
/** * * @Project: children-watch-web-api * @Class TokenLoginException 自定義的RuntimeException * @Description: tokenlogin過時時拋出 * @author cd 14163548@qq.com * @date 2018年1月24日 下午2:28:41 * @version V1.0 */ public class TokenLoginException extends RuntimeException { private static final long serialVersionUID = 1L; private String msg; public TokenLoginException(String msg) { super(); this.msg = msg; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
異常統一處理
/** * * @Project: children-watch-web-api * @Class ExceptionHandler 統一異常返回處理 * @Description: 統一異常返回處理 * @author cd 14163548@qq.com * @date 2018年1月24日 下午2:37:07 * @version V1.0 */ @ControllerAdvice @ResponseBody public class ExceptionHandle { private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class); /** * 500 - Token is invaild */ @ExceptionHandler(TokenLoginException.class) public R handleTokenException(Exception e) { logger.error("Token is invaild...", e); return R.error("Token is invaild"); } /** * 500 - Internal Server Error */ @ExceptionHandler(Exception.class) public R handleException(Exception e) { logger.error("Internal Server Error...", e); return R.error("Internal Server Error"); } /** * 404 - Internal Server Error */ @ExceptionHandler(NotFoundException.class) public R notHandleException(Exception e) { logger.error("Not Found Error...", e); return R.error("Not Found Error"); } }
aop攔截訪問是否忽略登錄檢查
/** * * @Project: children-watch-web-api * @Class SecurityAspect 安全檢查切面(是否登陸檢查) * @Description: 經過驗證Token維持登陸狀態 * @author cd 14163548@qq.com * @date 2018年1月24日 下午12:23:19 * @version V1.0 */ @Component @Aspect public class SecurityAspect { /** Log4j日誌處理 */ private static final Logger log = Logger.getLogger(SecurityAspect.class); @Autowired private TokenLoginService tokenLoginService; @Autowired private RedisCache redisCache; /** * 環繞通知 前後都通知 * aop檢測註解爲 RequestMapping 就調用此方法 * @param pjp * @return * @throws Throwable */ @Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)") public Object execute(ProceedingJoinPoint pjp) throws Throwable { // 從切點上獲取目標方法 MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); log.info("methodSignature : " + methodSignature); Method method = methodSignature.getMethod(); log.info("Method : " + method.getName() + " : " + method.isAnnotationPresent(IgnoreSecurity.class)); // 若目標方法忽略了安全性檢查,則直接調用目標方法 if(method.isAnnotationPresent(IgnoreSecurity.class)){ // 調用目標方法 return pjp.proceed(); } //忽略 api接口測試安全性檢查 if("getDocumentation".equalsIgnoreCase(method.getName())){ // 調用目標方法 return pjp.proceed(); } // 從 request header 中獲取當前 token String token = HttpContextUtils.getHttpServletRequest().getHeader(TokenConstant.LONGIN_TOKEN_NAME); // 檢查 token 有效性 if(!tokenLoginService.checkToken(token)){ String message = String.format("token [%s] is invalid", token); log.info("message : " + message); throw new TokenLoginException(message); } //每次調用接口就刷新過時時間 redisCache.expire(token, TokenConstant.TOKEN_EXPIRES_HOUR); // 調用目標方法 return pjp.proceed(); } }
一些工具類
public class HttpContextUtils { public static HttpServletRequest getHttpServletRequest() { return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); } } /** * rest * @Project: children-watch-commons * @Class TokenConstant * @Description: 接口登錄 token有效期 * @author cd 14163548@qq.com * @date 2018年1月24日 上午11:55:41 * @version V1.0 */ public class TokenConstant { /** * token有效期(秒) * 1天 */ public static final long TOKEN_EXPIRES_HOUR = 86400; /** 存放Token的header字段 */ public static final String LONGIN_TOKEN_NAME = "X-Token"; }
接口調用
@Controller public class WeChatLoginController extends BaseController{ /** * 本地測試 * @param openid * @return */ @RequestMapping("/login") @ResponseBody @IgnoreSecurity //忽略安全性檢查 public R login(String openid){ logger.info("**** openid **** : " + openid); if(StringUtils.isNotBlank(openid)){ //建立token String createToken = tokenLoginService.createToken(openid); logger.info("**** Generate Token **** : " + createToken); return R.ok(createToken); } return R.Empty(); } /** * 獲取openID * @return */ @RequestMapping("/openid") @ResponseBody public R getValue(HttpServletRequest request) { String openid = super.getOpenId(request); return R.ok(openid); } }
spring-context.xml中應配置掃描所有。
spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- spring掃描com.axq.watch.web.*.controller下面全部帶註解的類 --> <context:component-scan base-package="com.axq.watch.web" /> <!-- 默認servlet --> <mvc:default-servlet-handler /> <!-- 這個標籤表示使用註解來驅動 --> <mvc:annotation-driven/> <!-- 支持Controller的AOP代理 --> <aop:aspectj-autoproxy /> <bean id="mappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> </list> </property> </bean> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" /> <!-- 上傳 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="utf-8"></property> <property name="maxUploadSize" value="10485760000"></property> <property name="maxInMemorySize" value="40960"></property> </bean> </beans>
前端代碼
<html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width"> <script type="text/javascript" src="http://www.w3school.com.cn/jquery/jquery.js"></script> <script type="text/javascript"> $(document).ready(function(){ //登錄獲取到的token var token = ""; $("#b01").click(function(){ $.ajax({ // url:"http://localhost:8081/rest/itemcat/list?callback=getMessage", url:"http://localhost:8081/children-watch-web-api/login", type:"post", cache:false, data:{openid:"ocHCAwMdYLevTBbcYrKh07FJJ56E"}, dataType:"json", /**beforeSend: function(request) { request.setRequestHeader("X-Token", token); },*/ success:function(data){ var html = data.msg; token = html ; $("#myDiv").html("token:"+html); }, error:function(){ alert("發生異常"); } }); }); $("#b02").click(function(){ $.ajax({ url:"http://localhost:8081/children-watch-web-api/openid", //url:"http://localhost:8081/children-watch-web-api/config/list", //url:"http://localhost:8081/children-watch-web-api/student/list", type:"get", cache:false, dataType:"json", beforeSend: function(request) { request.setRequestHeader("X-Token", token); }, success:function(data){ var html = data.msg; $("#myDiv").html("openId:"+html); }, error:function(e){ alert("發生異常"+e); }, complete: function(XMLHttpRequest, status) { //請求完成後最終執行參數 alert(status); } }); }); }); </script> </head> <body> <div id="myDiv"><h2>經過 AJAX 改變文本</h2></div> <button id="b01" type="button">登錄</button> <button id="b02" type="button">查詢</button> </body> </html>
效果演示。
直接點查詢沒有token
點登錄 獲取了token
在點查詢 就能夠獲取到值了。
收工。