这篇文章描述SpringSecurity的跨域认证,通过前后端分离进行登陆认证,跨域调用资源。适合开发阶段使用的方式。
环境准备
- jdk1.7+
- Gradle2.8
- IntelliJ 15 or eclipse
- Redis
创建项目
通过idea创建spring-security-cors顶层模块,cors-resource服务端模块,cors-ui前端模块.目录结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| │ build.gradle │ settings.gradle ├─cors-resource │ │ build.gradle │ └─src │ ├─main │ │ ├─java │ │ └─resources │ └─test │ ├─java │ └─resources └─cors-ui │ build.gradle └─src ├─main │ ├─java │ └─resources └─test ├─java └─resources
|
顶层模块settings.gradle配置子模块信息:
1
| include "cors-resource", "cors-ui"
|
build.gradle配置通用信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| buildscript { ext { springBootVersion = '1.4.0.RELEASE' } repositories { mavenLocal() mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } subprojects{ group 'com.cjoop' version '1.0-SNAPSHOT' apply plugin: 'java' apply plugin: 'idea' apply plugin: 'eclipse' apply plugin: 'spring-boot'
sourceCompatibility = 1.7 targetCompatibility = 1.7 repositories { mavenLocal() mavenCentral() } dependencies { compile("org.springframework.boot:spring-boot-starter-web") testCompile("org.springframework.boot:spring-boot-starter-test") } }
|
跨域认证配置
通过Spring Security4.1.x提供的跨域支持进行配置,这里的认证结果进行了简单处理,在认证成功后返回sessionId给客户端。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| package com.cjoop.cors.config;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import com.cjoop.cors.handler.AuthenticationFailureHandler; import com.cjoop.cors.handler.AuthenticationSuccessHandler; import com.cjoop.cors.handler.LogoutSuccessHandler;
@Configuration @EnableWebSecurity public class SpringSecurityAutoConfiguration extends WebSecurityConfigurerAdapter{ @Autowired @Qualifier("authenticationSuccessHandler") private AuthenticationSuccessHandler authenticationSuccessHandler; @Autowired @Qualifier("authenticationFailureHandler") private AuthenticationFailureHandler authenticationFailureHandler; @Autowired @Qualifier("logoutSuccessHandler") private LogoutSuccessHandler logoutSuccessHandler;
@Override protected void configure(HttpSecurity http) throws Exception { String loginProcessingUrl = "/j_spring_security_check"; http.cors(); http.csrf().disable() .authorizeRequests() .antMatchers("/index.html", "/index.js","/","/favicon.ico").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .loginProcessingUrl(loginProcessingUrl) .successHandler(authenticationSuccessHandler) .failureHandler(authenticationFailureHandler) .permitAll() .and() .logout().logoutSuccessHandler(logoutSuccessHandler) .permitAll(); } }
|
通过Spring MVC提供的跨域支持进行配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.cjoop.cors.config;
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration public class WebMvcAutoConfiguration extends WebMvcConfigurerAdapter{ @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedHeaders("x-requested-with","x-auth-token","content-type") .maxAge(3600) .allowedOrigins("*") .allowCredentials(true); } }
|
认证完成后我们的sessionId是被持久化到了redis中,每一次浏览器调用资源请求都会携带X-Auth-Token和X-Requested-With两个头信息。这里我们实现一个跨域的会话策略CorsHttpSessionStrategy,这个策略合并了CookieHttpSessionStrategy和HeaderHttpSessionStrategy,以便提供本地策略和跨域策略的支持。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| package com.cjoop.cors.session;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.springframework.session.Session; import org.springframework.session.web.http.CookieHttpSessionStrategy; import org.springframework.session.web.http.HeaderHttpSessionStrategy; import org.springframework.session.web.http.MultiHttpSessionStrategy;
public class CorsHttpSessionStrategy implements MultiHttpSessionStrategy{ CookieHttpSessionStrategy cookieHttpSessionStrategy = new CookieHttpSessionStrategy(); HeaderHttpSessionStrategy headerHttpSessionStrategy = new HeaderHttpSessionStrategy(); @Override public String getRequestedSessionId(HttpServletRequest request) { String sessionId = headerHttpSessionStrategy.getRequestedSessionId(request); if(null==sessionId){ sessionId = cookieHttpSessionStrategy.getRequestedSessionId(request); } return sessionId; }
@Override public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) { headerHttpSessionStrategy.onNewSession(session, request, response); cookieHttpSessionStrategy.onNewSession(session, request, response); }
@Override public void onInvalidateSession(HttpServletRequest request, HttpServletResponse response) { headerHttpSessionStrategy.onInvalidateSession(request, response); cookieHttpSessionStrategy.onInvalidateSession(request, response); }
@Override public HttpServletRequest wrapRequest(HttpServletRequest request, HttpServletResponse response) { return cookieHttpSessionStrategy.wrapRequest(request, response); }
@Override public HttpServletResponse wrapResponse(HttpServletRequest request, HttpServletResponse response) { return cookieHttpSessionStrategy.wrapResponse(request, response); } }
|
项目cors-resource(application.properties)提供一个账户信息user来进行登陆,服务端口设置为9000:
1 2
| server.port=9000 security.user.password=password
|
前端代码的准备
cors-ui项目中创建首页内容index.html,用来展示后端请求的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| <!DOCTYPE html> <html ng-app="spring-security"> <head> <meta charset="UTF-8"> <title>首页</title> <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css"> <style type="text/css"> .container{ padding-top: 50px; } </style> </head> <body ng-controller="IndexController"> <div class="container"> <div class="row"> <h3>Spring Security 跨域调用</h3> </div> <div class="row"> <div class="col-md-12"> <form class="form-horizontal" role="form"> <div class="form-group"> <label for="username" class="col-md-2 control-label">Username:</label> <div class="col-md-6"> <input type="text" class="form-control" id="username" name="username" ng-model="credentials.username"/> </div> </div> <div class="form-group"> <label for="password" class="col-md-2 control-label">Password:</label> <div class="col-md-6"> <input type="password" class="form-control" id="password" name="password" ng-model="credentials.password"/> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <a class="btn btn-primary" ng-click="resource()">resource</a> <a class="btn btn-primary" ng-click="login()">login</a> <a class="btn btn-primary" ng-click="logout()">logout</a> </div> </div> </form> </div> </div> <div class="row"> <span ng-bind="message" style="color:red;"></span> </div> <div class="row"> <h1>Greeting</h1> <div> <p>The ID is {{greeting.id}}</p> <p>The content is {{greeting.content}}</p> <p>The sessionid is {{greeting.sessionId}}</p> <p>The username is {{greeting.user.username}}</p> </div> </div> </div> <script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script> <script src="//cdn.bootcss.com/angular.js/1.5.8/angular.min.js"></script> <script src="//cdn.bootcss.com/angular.js/1.5.8/angular-cookies.min.js"></script> <script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> <script src="index.js"></script> </body> </html>
|
index.js通过ajax认证获取sessionId保存到cookies中,这样在刷新页面以后也能够获取到sessionId,每一个资源访问的头信息中都会携带X-Auth-Token和X-Requested-With。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| (function(angular,$){ 'use strict'; window.contextPath = "http://localhost:9000"; angular.module('spring-security', ['ngCookies']) .run(['$http', '$cookies', function($http, $cookies) { $http.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; $http.defaults.headers.common['X-Auth-Token'] = $cookies.get('X-Auth-Token'); }]) .controller('IndexController', ['$scope','$http','$cookies',function($scope,$http,$cookies) { $scope.credentials = {}; $scope.login = function(){ return $http.post(contextPath+"/j_spring_security_check",null,{ params: $scope.credentials }).then(function (resp) { var result = resp.data; $scope.message = result.text; if(result.code == 0){ $cookies.put('X-Auth-Token',result.data); $http.defaults.headers.common['X-Auth-Token'] = result.data; } }); } $scope.logout = function() { $http.post(contextPath+'/logout', {}).then(function(resp){ $scope.message = resp.data.text; $scope.greeting = {}; }); } $scope.resource = function(){ $http.get(contextPath+'/resource').then(function(resp) { if(resp.data.code==-500){ $scope.message = resp.data.text; } $scope.greeting = resp.data; }); } }]); })(angular,jQuery);
|
测试程序
启动redis服务,执行命令 gradle bootRun 运行cors-resource和cors-ui两个项目,访问地址http://localhost:9000 体验本地调用效果,访问地址http://localhost:8080/ 体验跨域调用效果。 ,输入用户信息进行登陆,然后调用resource服务,结果如图:

以上就是结合Spring Security进行前后端分离开发时候的调用思路。
