我正在使用Spring开发Java API,对此我有两种验证方式。
这是在src / main / resources / META-INF / spring / applicationContex.security.xml中定义的方式:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd">
<!-- HTTP security configurations -->
<http auto-config="true" use-expressions="true">
<form-login login-processing-url="/resources/j_spring_security_check" login-page="/login" authentication-failure-url="/login?login_error=t"
/>
<logout logout-url="/resources/j_spring_security_logout" />
<!-- Back office routes -->
<intercept-url pattern="/users/**" access="hasRole('ADM')" />
<intercept-url pattern="/login/**" access="permitAll" />
<!-- ... -->
<!-- front office routes -->
<intercept-url pattern="/resources/**" access="permitAll" />
<intercept-url pattern="/static/**" access="permitAll" />
<intercept-url pattern="/p/login" access="permitAll" />
<intercept-url pattern="/p/new" access="permitAll" />
<!-- ... -->
<!-- set the expected method to prevent from OPTIONS queries to be rejected because of no Authentication header -->
<intercept-url pattern="/p/logout" access="isAuthenticated()" method="POST"/>
<intercept-url pattern="/**" access="permitAll" method="OPTIONS"/>
<intercept-url pattern="/**" access="isAuthenticated()" />
<!-- Concurrent Session Control -->
<session-management session-authentication-error-url="/sessionExpired" >
<concurrency-control max-sessions="1"/>
</session-management>
</http>
<!-- Configure bakc office Authentication mechanism -->
<beans:bean name="adminAuthenticationProvider" class="com.xxx.security.AdminAuthenticationProvider">
</beans:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="adminAuthenticationProvider" />
</authentication-manager>
</beans:beans>
这是src / main / webapp / WEB-INF / web.xml内容:
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.5" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>myAPI</display-name>
<description>my description</description>
<!-- Enable escaping of form submission contents -->
<context-param>
<param-name>defaultHtmlEscape</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:META-INF/spring/applicationContext*.xml</param-value>
</context-param>
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>AuthenticationFilter</filter-name>
<filter-class>com.xxx.security.JwtTokenFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AuthenticationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Concurrent Session Control -->
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Handles Spring requests -->
<servlet>
<servlet-name>myAPI</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/spring/webmvc-config.xml</param-value>
</init-param>
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>myAPI</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>60</session-timeout>
</session-config>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/uncaughtException</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/resourceNotFound</location>
</error-page>
</web-app>
问题出在标头中需要令牌的路由。 我创建了一个过滤器来管理对API的所有调用:
以下是过滤器:
public class JwtTokenFilter extends GenericFilterBean {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String authKey = null;
List<GrantedAuthority> authList = null;
User principal = null;
Authentication auth = null;
/* OPTIONS request are not authenticated, so do not manage them */
if(!HttpMethod.OPTIONS.matches(httpServletRequest.getMethod())) {
authKey = httpServletRequest.getHeader("authorization");
if(null != authKey) {
if(authKey.toLowerCase().startsWith("bearer ")) {
authKey = authKey.substring("bearer ".length());
request.setAttribute("authorization", authKey);
}
if(WebServiceAnswer.STATUS_OK == TokenManager.checkToken(authKey).getStatus()) {
authList = new ArrayList<GrantedAuthority>();
authList.add(new SimpleGrantedAuthority(Roles.getRoleUser()));
principal = new User(authKey, "", authList);
auth = new UsernamePasswordAuthenticationToken(principal, "", authList);
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
}
// continue to process default behavior
chain.doFilter(request, response);
SecurityContextHolder.getContext().setAuthentication(null);
}
}
过滤器完成工作后,应该交给控制器:
@RequestMapping("/p")
@Controller
@RooWebScaffold(path = "p", formBackingObject = P.class)
public class PController {
...
@CrossOrigin(origins = { "http://localhost:4200", "http://www.xxx.xx" })
@RequestMapping(value = "/logout", method = RequestMethod.POST)
public ResponseEntity<String> logout(@RequestHeader("Authorization") String authKey) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json; charset=utf-8");
P p = null;
WebServiceAnswer answer = new WebServiceAnswer();
JSONObject answerData;
String token = null;
// get expected p
p = P.findByToken(authKey);
// activation key not found
if (null == p) {
...
return new ResponseEntity<String>(answer.toJsonString(), headers, HttpStatus.BAD_REQUEST);
}
// authentication key found
...
return new ResponseEntity<String>(answer.toJsonString(), headers, HttpStatus.ACCEPTED);
}
}
我调试了过滤器,并且在doFitler()调用中一切正常,或者至少每行都没有问题。
问题是,如果我对/ p / logout路由执行POST(例如),并在标头中包含预期的令牌,则不会运行控制器方法PController.logout()。
如果我更新applicationContext.xml,并设置它:
<intercept-url pattern="/p/logout" access="permitAll" method="POST"/>
然后调用controller方法。
我在无状态身份验证中错过了什么,因此在需要时请调用控制器?
编辑 我尝试了另一种方法来管理来自前台站点的无状态请求:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd">
<!-- Configure front office authorization mechanism -->
<beans:bean name="jwtTokenFilter" class="com.xxx.security.JwtTokenFilter"></beans:bean>
<http security="none" pattern="/p/login"></http>
<http security="none" pattern="/p/new"></http>
<http security="none" pattern="/p/emailConfirm"></http>
<http security="none" pattern="/p/sendNewActivationKey"></http>
<http security="none" pattern="/p/sendResetPasswordEmail"></http>
<http security="none" access-decision-manager-ref="jwtTokenFilter" pattern="/p/resetPassword"></http>
<http auto-config="true" use-expressions="true" pattern="/p/logout" >
<custom-filter ref="jwtTokenFilter" position="FIRST"/>
<intercept-url pattern="/p/logout" access="permitAll" method="OPTIONS"/>
<intercept-url pattern="/p/logout" access="isAuthenticated()" method="POST"/>
</http>
<!-- Configure back office authentication mechanism -->
<http auto-config="true" use-expressions="true">
<form-login
login-processing-url="/resources/j_spring_security_check"
login-page="/login"
authentication-failure-url="/login?login_error=t"
/>
<logout logout-url="/resources/j_spring_security_logout" />
<!-- Configure these elements to secure application URIs -->
<intercept-url pattern="/resources/**" access="permitAll" />
<intercept-url pattern="/static/**" access="permitAll" />
<intercept-url pattern="/login/**" access="permitAll" />
<intercept-url pattern="/users/**" access="hasRole('ROLE_ADMIN')" />
...
<!-- Concurrent Session Control -->
<session-management session-authentication-error-url="/sessionExpired" >
<concurrency-control max-sessions="1"/>
</session-management>
</http>
<beans:bean name="adminAuthenticationProvider" class="com.xxx.security.AdminAuthenticationProvider"></beans:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="adminAuthenticationProvider" />
</authentication-manager>
</beans:beans>
public class JwtTokenFilter extends GenericFilterBean {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if(this.isRequestAuthorized(request, response, chain)) {
// continue to process default behavior
chain.doFilter(request, response);
SecurityContextHolder.getContext().setAuthentication(null);
}
}
private boolean isRequestAuthorized(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String verb = httpServletRequest.getMethod();
String route = httpServletRequest.getRequestURI();
String authKey = null;
WebServiceAnswer answer = null;
String message = null;
List<GrantedAuthority> authList = null;
User principal = null;
Authentication auth = null;
// OPTIONS request are not authenticated, so do not manage them
if(HttpMethod.OPTIONS.matches(verb)) {
return true;
}
// if we are here, we should have an authorization token
authKey = httpServletRequest.getHeader("authorization");
if(null == authKey) {
message = String.format("%s requests to route %s require authorization ; Not token found in headers", verb, route);
((HttpServletResponse)response).setStatus(HttpStatus.UNAUTHORIZED.value());
answer = new WebServiceAnswer();
answer.setStatus(WebServiceAnswer.STATUS_KO);
answer.setHttpStatus(HttpStatus.UNAUTHORIZED);
answer.setCode("AUTHORIZATION_ERROR-NO_AUTH_KEY");
answer.setHint(message);
((HttpServletResponse)response).getWriter().write(new ObjectMapper().writeValueAsString((answer)));
return false;
}
// check authKey
if(authKey.toLowerCase().startsWith("bearer ")) {
authKey = authKey.substring("bearer ".length());
request.setAttribute("authorization", authKey);
}
answer = TokenManager.checkToken(authKey);
if(WebServiceAnswer.STATUS_OK == answer.getStatus()) {
// authKey is valid: authorize the request
authList = new ArrayList<GrantedAuthority>();
authList.add(new SimpleGrantedAuthority(Roles.getRoleUser()));
principal = new User(authKey, "", authList);
auth = new UsernamePasswordAuthenticationToken(principal, "", authList);
SecurityContextHolder.getContext().setAuthentication(auth);
((HttpServletResponse)response).setStatus(HttpStatus.OK.value());
((HttpServletResponse)response).getWriter().write(new ObjectMapper().writeValueAsString((answer)));
return true;
}
// authKey is not valid: reject the request
((HttpServletResponse)response).setStatus(HttpStatus.UNAUTHORIZED.value());
((HttpServletResponse)response).getWriter().write(new ObjectMapper().writeValueAsString((answer)));
return false;
}
}
在这里,我的行为与以前完全相同:( 如果未提供令牌或令牌无效,则我具有响应的预期http状态代码,但我没有答案内容:(
如果令牌已给出并处于活动状态,则不会调用控制器...
注意:在applicationContext-security中,如果我这样设置注销路径:
<http security="none" access-decision-manager-ref="jwtTokenFilter" pattern="/p/logout"></http>
然后不调用过滤器,而是调用控制器方法IS。
我非常确定问题在于过滤链中的身份验证理解(过滤链无法理解请求已被授权,因此未调用控制器)或过滤器给予的授权太晚了(之后,系统决定不调用控制器,原因是该请求未被授权。
任何人都可以帮忙吗?