我正在编写一个Java Spring MVC 4 REST应用程序,该应用程序位于前端设备(网站,移动应用程序等)和数据库之间。我在下面的代码将为每个请求创建一个新会话(因为REST是无状态的),查看请求的Authorization标头,并确认令牌有效并请求经过身份验证。
当用户请求没有有效令牌的安全方法时,我希望将未经授权的请求从500 Access Is Denied消息重定向到401 Unauthorized消息。
这是我到目前为止所做的。
AccessDeniedHandler:
public class Unauthorized401AccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException)
throws IOException, ServletException {
response.setStatus(401);
}
}
WebSecurityConfigurerAdapter:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.accessDeniedHandler(new Unauthorized401AccessDeniedHandler());
}
}
过滤器:
public class SecurityFilter implements Filter {
final static Logger logger = Logger.getLogger(SecurityFilter.class);
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession();
String requestUri = request.getRequestURI();
session.invalidate();
SecurityContextHolder.clearContext();
session = request.getSession(true); // create a new session
SecurityContext ctx = SecurityContextHolder.createEmptyContext();
boolean isLoggedIn = false;
String token = null;
String authorizationHeader = request.getHeader("authorization");
if(authorizationHeader != null && authorizationHeader.startsWith("bearer")) {
String encryptedToken = authorizationHeader.split(" ")[1];
token = StringUtils.newStringUtf8(Base64.decodeBase64(encryptedToken));
// confirm user is logged in and authorized here TBD
isLoggedIn = true;
}
PreAuthenticatedAuthenticationToken authentication = null;
if(isLoggedIn) {
SessionCredentialsModel authRequestModel = new SessionCredentialsModel();
authRequestModel.employeeId = 323;
authRequestModel.firstName = "Danny";
authRequestModel.lastName = "Boy";
authRequestModel.token = "this_is_a_test_token";
authentication = new PreAuthenticatedAuthenticationToken(authRequestModel, token);
} else {
authentication = new PreAuthenticatedAuthenticationToken(new SessionCredentialsModel(), null);
}
authentication.setAuthenticated(true);
ctx.setAuthentication(authentication);
SecurityContextHolder.setContext(ctx);
chain.doFilter(req, res);
}
安全模型(又名安全上下文主体):
public class SessionCredentialsModel {
public int employeeId;
public String firstName;
public String lastName;
public String token;
public boolean isAuthenticated() {
if(employeeId > 0 && token != null) {
return true;
}
return false;
}
}
最后是控制器:
@RequestMapping(value = "/", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("principal.isAuthenticated()")
public ResponseEntity<LoginResponseModel> create() {
LoginResponseModel responseModel = new LoginResponseModel();
responseModel.statusCode = 55;
responseModel.token = "authorized model worked!";
return new ResponseEntity<LoginResponseModel>(responseModel, HttpStatus.OK);
}
当我运行没有Authorization标头的方法时,我得到了这个错误(而不是我想要得到的错误):
HTTP Status 500 - Request processing failed; nested exception is org.springframework.security.access.AccessDeniedException: Access is denied
type Exception report
message Request processing failed; nested exception is org.springframework.security.access.AccessDeniedException: Access is denied
description The server encountered an internal error that prevented it from fulfilling this request.
exception
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.security.access.AccessDeniedException: Access is denied
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
net.pacificentertainment.middletier.app.security.SecurityFilter.doFilter(SecurityFilter.java:73)
root cause
org.springframework.security.access.AccessDeniedException: Access is denied
org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84)
org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233)
org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:65)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656)
net.pacificentertainment.middletier.app.controllers.EmployeeController$$EnhancerBySpringCGLIB$$b6765b64.create(<generated>)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
net.pacificentertainment.middletier.app.security.SecurityFilter.doFilter(SecurityFilter.java:73)
note The full stack trace of the root cause is available in the Apache Tomcat/8.0.43 logs.
Apache Tomcat/8.0.43
我不知道为什么我无法获得未经授权的请求以返回401 - 或500以外的任何其他状态代码。
您怎么看?
答案 0 :(得分:7)
好的伙伴们,无法从社区获得任何帮助但我确实找到了解决方案 - 虽然它不是一个直接的解决方案。
@ControllerAdvice
public class SecurityExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({AccessDeniedException.class})
public ResponseEntity<Object> handleAccessDeniedException(Exception ex, WebRequest request) {
if(ex.getMessage().toLowerCase().indexOf("access is denied") > -1) {
return new ResponseEntity<Object>("Unauthorized Access", new HttpHeaders(), HttpStatus.UNAUTHORIZED);
}
return new ResponseEntity<Object>(ex.getMessage(), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
我的应用程序中的这个新文件将允许我控制异常期间发生的事情。现在我可以手动检查问题,看看是否&#34;访问被拒绝&#34;然后重定向到401工作。上面的问题是重定向到401的代码没有被击中。此代码已执行。
同样,这并不是一个直接的解决方案,因为我们正在操纵不同的Spring MVC以及一种黑客行为的默认行为。
如果有人有更优雅的解决方案,请发帖。
答案 1 :(得分:2)
出现此错误是因为 java 的 AuthenticationEntryPoint 可以用 500 状态覆盖或映射错误。
您可以使用在
中实现的下一个代码来解决这个问题
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException ) throws IOException, ServletException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getOutputStream().println("{ \"error\": \"" + authException.getMessage() + "\" }");
}
@ExceptionHandler(value = { AccessDeniedException.class })
public void commence(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex ) throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getOutputStream().println("{ \"error\": \"" + ex.getMessage() + "\" }");
}
使用此代码,您可以捕获 @PreAuthorize annotacion 抛出的 AccessDeniedException
答案 2 :(得分:0)
我假设您没有使用Spring Boot-可能是因为您没有在容器中注册springSecurityFilterChain
吗?
堆栈跟踪不显示任何Spring Security过滤器被调用。您的自定义AccessDeniedHandler
将在位于ExceptionTranslationFilter
的{{1}}中被调用。但是看来您的方法安全拦截器正在抛出springSecurityFilterChain
,而该陷阱不会在任何地方被捕获。
答案 3 :(得分:0)
我还得到了一个HTTP 500,我期望它是403。对我来说,这是Spring 5中Spring Security配置错误的结果。我将过滤器链配置为允许对import i18nTranslatorForJsonTemplates from "i18n-translator-for-json-templates";
const result = i18nTranslatorForJsonTemplates(en_lang, template);
console.log(JSON.stringify(result));
/*output
{
"platformType": "Android",
"ownershipMode": "COBO",
"version": 1,
"description": "Command Schema For 'Company Owned/Business Only'",
"label": "COBO3"
}
*/
端点的所有请求。
/pages
同时,我在同一http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(preAuthFilter, BasicAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/pages/**", "/sessions/").permitAll()
.anyRequest()
.authenticated();
端点上添加了@PreAuthorize
注释。
/pages
这将导致@PreAuthorize("hasAuthority('ORG_ADMIN')")
@GetMapping("/pages/{uniqueUrlName}")
public ResponseEntity<Page> page(@PathVariable("uniqueUrlName") String uniqueUrlName) {
// implementation here
}
被跳过,然后org.springframework.security.web.access.ExceptionTranslationFilter
被作为常规应用程序异常处理,在我的情况下,是由AccessDeniedException
类中的@ExceptionHandler
方法处理的。
删除@ControllerAdvice
配置可解决此问题。现在,我用未经身份验证的请求访问端点时看到403。