先後端分離之接口登錄權限token

  隨着業務的需求普通的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

 

在點查詢 就能夠獲取到值了。

 

 

收工。

相關文章
相關標籤/搜索