手动validation使用弹簧安全性
我正在使用spring security并且工作正常,但是现在我想手动启动安全过程,对客户端进行更改我需要在控制器中获取用户名和密码(表单不会直接调用“j_spring_security_check”)
我想到了两个选项,我有一些问题:
-
在我获取参数并执行某些操作后,我将向j_spring_security_check url发送一个发布请求。 我的代码:
public void test(loginDTO loginDTO){
MultiValueMap body = new LinkedMultiValueMap(); HttpHeaders headers = new HttpHeaders(); body.add( "j_username", loginDTO.getJ_username()); body.add( "j_password", loginDTO.getJ_password()); HttpEntity httpEntity = new HttpEntity( body, headers); headers.add( "Accept", MediaType.APPLICATION_JSON_VALUE); restTemplate.exchange( "http://localhost:8080/XXX/j_spring_security_check", HttpMethod.POST, httpEntity, HttpServletResponse.class); }
这不起作用,我得到:500内部服务器错误为什么?
-
第二种选择 – 我做了以下事情:
public void test2(loginDTO loginDTO, HttpServletRequest request) { UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( loginDTO.getJ_username(), loginDTO.getJ_password()); token.setDetails(new WebAuthenticationDetails(request)); Authentication authentication = this.authenticate(token); SecurityContextHolder.getContext().setAuthentication(authentication); this.sessionRegistry.registerNewSession( request.getSession().getId(), authentication.getPrincipal()); }
问题是没有调用onAuthenticationSuccess。 感觉不对,我错过了使用弹簧安全的重点。
什么是正确的原因?
我通常会做以下事情:
@Controller public class AuthenticationController { @Autowired AuthenticationManager authenticationManager; @Autowired SecurityContextRepository securityContextRepository; @RequestMapping(method = Array(RequestMethod.POST), value = Array("/authenticate")) public String authenticate(@RequestParam String username, @RequestParam String password, HttpServletRequest request, HttpServletResponse response) { Authentication result = this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); SecurityContextHolder.getContext.setAuthentication(result); this.securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response); return "successView"; } }
使用这种方法的原因是:
- 非常简单,只需几行代码就可以忽略exception处理等等。
- 利用现有的Spring Security组件。
- 使用在应用程序配置中配置的Spring Security组件,并允许在需要时更改它们。 例如,可以针对RDBMS,LDAP,Web服务,Active Directory等进行身份validation,而无需担心自定义代码。
当您想要从正常的身份validation过程尽可能多地使用时,您可以创建一个模拟的HttpServletRequest
和HttpServletResponse
( org.springframework.mock.web.MockHttpServletRequest
和org.springframework.mock.web.MockHttpServletResponse
),其中包含登录名和密码,然后调用
UsernamePasswordAuthenticationFilter.attemptAuthentication( HttpServletRequest request, HttpServletResponse response)`
之后你还需要调用SessionAuthenticationStrategy.onAuthentication(..)
和SessionAuthenticationStrategy.onAuthentication(..)
successfulAuthentication(..)
这有点棘手,因为私有文件,所以这是我的解决方案:
public class ExtendedUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { @Override public void manualAuthentication(String login, String password, HttpServletRequest httpServletRequest) throws IOException, ServletException { /** I do not mock the request, I use the existing request and manipulate them*/ AddableHttpRequest addableHttpRequest = new AddableHttpRequest(httpServletRequest); addableHttpRequest.addParameter("j_username", login); addableHttpRequest.addParameter("j_password", password); MockHttpServletResponse mockServletResponse = new MockHttpServletResponse(); Authentication authentication = this.attemptAuthentication( addableHttpRequest, mockServletResponse); this.reflectSessionStrategy().onAuthentication( authentication, addableHttpRequest, mockServletResponse); this.successfulAuthentication(addableHttpRequest, mockServletResponse, authentication); } private SessionAuthenticationStrategy reflectSessionStrategy() { Field sessionStrategyField = ReflectionUtils.findField( AbstractAuthenticationProcessingFilter.class, "sessionStrategy", SessionAuthenticationStrategy.class); ReflectionUtils.makeAccessible(sessionStrategyField); return (SessionAuthenticationStrategy) ReflectionUtils.getField(sessionStrategyField, this); } }
AddableHttpRequest
就像一个基于真实请求的模拟
public class AddableHttpRequest extends HttpServletRequestWrapper { /** The params. */ private HashMap params = new HashMap(); public AddableHttpRequest(HttpServletRequest request) { super(request); } @Override public String getMethod() { return "POST"; } @Override public String getParameter(final String name) { // if we added one, return that one if (params.get(name) != null) { return params.get(name); } // otherwise return what's in the original request return super.getParameter(name); } public void addParameter(String name, String value) { params.put(name, value); } }
另一种方法是实现自己的身份validationfilter。 这是一个调用AuthenticationManager.authenticate(Authentication authentication)
。 但是这个类还负责调用身份validation的所有东西( AbstractAuthenticationProcessingFilter.doFilter
做什么)
好的,所以我结合了@Ralph和@manish的答案,这就是我做的:
(twoFactorAuthenticationFilter是UsernamePasswordAuthenticationFilter的扩展)
public void manualAuthentication(loginDTO loginDTO, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { AddableHttpRequest addableHttpRequest = new AddableHttpRequest( request); addableHttpRequest.addParameter( "j_username", loginDTO.getJ_username()); addableHttpRequest.addParameter( "j_password", loginDTO.getJ_password()); UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) twoFactorAuthenticationFilter.attemptAuthentication( addableHttpRequest, response); if (token.isAuthenticated()) { twoFactorAuthenticationFilter.successfulAuthentication( addableHttpRequest, response, null, token); } }
它工作正常