我正在尝试通过Spring Security。我必须实现自定义登录表单,所以我需要很好地理解我的配置意味着什么。
弹簧security.xml文件
<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-4.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-4.0.xsd">
<http auto-config="true">
<intercept-url pattern="/user**" access="isAuthenticated()" />
<form-login authentication-failure-url="/login" login-page="/login"
login-processing-url="/login" default-target-url="/user" />
<logout invalidate-session="true" logout-success-url="/index"
logout-url="/logout" />
</http>
<authentication-manager id="custom-auth">
<authentication-provider>
<user-service>
<user name="my_username" password="my_password"
authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
的LoginController
@Controller
public class LoginController {
[....]
@RequestMapping(value = "/login", method = RequestMethod.POST)
public ModelAndView doLogin() {
System.out.println("***LOGIN_POST***");
return new ModelAndView("users/home");
}
@RequestMapping(value = "/logout", method = RequestMethod.POST)
public ModelAndView doLogout() {
System.out.println("***LOGOUT_POST***");
return new ModelAndView("index");
}
}
我知道我可以使用RequestMethod.GET映射/ login URL,但是当我在表单提交后尝试拦截POST时它不起作用。
UserDetailsServiceImpl
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private CustomerDao customerDao;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
Customer customer = customerDao.findCustomerByUsername(username);
return new User(customer.getUsername(), customer.getPassword(), true, true, true, true,
Arrays.asList(new SimpleGrantedAuthority(customer.getRole())));
}
}
N.B。 用户的数据最初不在我的数据库中,这是因为我不确定UserDetailsService解决方案(其中UserDetails只是通过用户名加载)。要检索我的Customer对象,我需要用户名和密码(发送到特定的外部URL),然后,如果JSON响应是肯定的(用户名和密码是正确的),我必须发送2个其他HTTP请求以获取客户的数据作为名字,姓氏,国籍等。此时我的用户可以被视为已登录。
有什么建议吗? 提前谢谢。
答案 0 :(得分:6)
- 我相信,但需要确认,那是因为安全正在做 幕后的东西:从中获取用户名和密码值 发布的表单并将其与身份验证中的表单进行比较 provider:如果匹配,则显示default-target-url,否则显示user 必须重复登录。是不是?
醇>
没错。在安全配置中声明<login-form>
元素时,您正在配置UsernamePasswordAuthenticationFilter。
你配置了一些网址:
@RequestMapping
的网址spring-security
相当于构建后处理控制器方法。虽然login-processing-url,default-target-url和authentication-failure-url必须是有效的RequestMappings,但login-processing-url将不会到达Spring MVC控制器层,因为它在执行Spring MVC之前执行调度员servlet。
所以
@RequestMapping(value = "/login", method = RequestMethod.POST)
public ModelAndView doLogin() {
System.out.println("***LOGIN_POST***");
return new ModelAndView("users/home");
}
永远不会到达。
将帖子归档到/login
uri时,UsernamePasswordAuthenticationFilter将执行它的doFilter()
方法来捕获用户提供的凭据,构建UsernamePasswordAuthenticationToken并将其委托给AuthenticationManager ,此Authentication将在匹配的AuthenticationProvider中执行。
- 然后我的问题是:我需要在安全性的登录表单中输入用户名和密码值,因为我必须向外部服务器发送HTTP请求以验证这些是否匹配。在介绍安全性之前,我使用/ login GET和/ login POST,使用@ModelAttribute注释开发了这个机制。我现在该怎么办? 我想你曾经对外部服务器执行身份验证时通过从POST /登录RequestMapping委托给一个类来完成它。
醇>
因此,只需创建一个自定义AuthenticationProvider,它将用户验证内容委托给您的旧逻辑:
public class ThirdPartyAuthenticationProvider implements AuthenticationProvider {
private Class<? extends Authentication> supportingClass = UsernamePasswordAuthenticationToken.class;
// This represents your existing username/password validation class
// Bind it with an @Autowired or set it in your security config
private ExternalAuthenticationValidator externalAuthenticationValidator;
/* (non-Javadoc)
* @see org.springframework.security.authentication.AuthenticationProvider#authenticate(org.springframework.security.core.Authentication)
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
boolean validated = this.externalAuthenticationValidator.validate(authentication.getName(), authentication.getCredentials().toString());
if(!validated){
throw new BadCredentialsException("username and/or password not valid");
}
Collection<? extends GrantedAuthority> authorities = null;
// you must fill this authorities collection
return new UsernamePasswordAuthenticationToken(
authentication.getName(),
authentication.getCredentials(),
authorities
);
}
/* (non-Javadoc)
* @see org.springframework.security.authentication.AuthenticationProvider#supports(java.lang.Class)
*/
@Override
public boolean supports(Class<?> authentication) {
return this.supportingClass.isAssignableFrom(authentication);
}
public ExternalAuthenticationValidator getExternalAuthenticationValidator() {
return externalAuthenticationValidator;
}
public void setExternalAuthenticationValidator(ExternalAuthenticationValidator externalAuthenticationValidator) {
this.externalAuthenticationValidator = externalAuthenticationValidator;
}
}
安全配置xml:
<beans:bean id="thirdPartyAuthenticationProvider" class="com.xxx.yyy.ThirdPartyAuthenticationProvider">
<!-- here set your external authentication validator in case you can't autowire it -->
<beans:property name="externalAuthenticationValidator" ref="yourExternalAuthenticationValidator" />
</beans:bean>
<security:authentication-manager id="custom-auth">
<security:authentication-provider ref="thirdPartyAuthenticationProvider" />
</security:authentication-manager>
<security:http auto-config="true" authentication-manager-ref="custom-auth">
<security:intercept-url pattern="/user**" access="isAuthenticated()" />
<security:form-login authentication-failure-url="/login" login-page="/login"
login-processing-url="/login" default-target-url="/user" />
<security:logout invalidate-session="true" logout-success-url="/index"
logout-url="/logout" />
<!-- in spring security 4.x CSRF filter is enabled by default. Disable it if
you don't plan to use it, or at least in the first attempts -->
<security:csrf disabled="true"/>
</security:http>
- 使用实现UserDetailsService的类更改身份验证提供程序,会发生什么?我相信,在这种情况下,登录表单中输入的用户名和密码将与从db检索到的用户名和密码进行比较,因为这些用户名和密码已分配给User对象。这样对吗?
醇>
正如您所说,您必须同时发送用户名和密码,我认为UserServiceDetails架构并不符合您的要求。我很瘦,你应该按照我在第2点的建议去做。
编辑:
最后一件事:现在我在身份验证方法中发送HTTP请求, 如果凭据是正确的,我会在响应中收到一个令牌,我就是 需要访问其他外部服务器服务。我怎么能通过 它在我的Spring控制器中?
要接收和处理收到的令牌,我会这样做:
ExternalAuthenticationValidator接口:
public interface ExternalAuthenticationValidator {
public abstract ThirdPartyValidationResponse validate(String name, String password);
}
ThirdPartyValidationResponse模型界面:
public interface ThirdPartyValidationResponse{
public boolean isValid();
public Serializable getToken();
}
然后,更改提供商处理和管理它的方式:
public class ThirdPartyAuthenticationProvider implements AuthenticationProvider {
private Class<? extends Authentication> supportingClass = UsernamePasswordAuthenticationToken.class;
private ExternalAuthenticationValidator externalAuthenticationValidator;
/* (non-Javadoc)
* @see org.springframework.security.authentication.AuthenticationProvider#authenticate(org.springframework.security.core.Authentication)
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
ThirdPartyValidationResponse response = this.externalAuthenticationValidator.validate(authentication.getName(), authentication.getCredentials().toString());
if(!response.isValid()){
throw new BadCredentialsException("username and/or password not valid");
}
Collection<? extends GrantedAuthority> authorities = null;
// you must fill this authorities collection
UsernamePasswordAuthenticationToken authenticated =
new UsernamePasswordAuthenticationToken(
authentication.getName(),
authentication.getCredentials(),
authorities
);
authenticated.setDetails(response);
return authenticated;
}
/* (non-Javadoc)
* @see org.springframework.security.authentication.AuthenticationProvider#supports(java.lang.Class)
*/
@Override
public boolean supports(Class<?> authentication) {
return this.supportingClass.isAssignableFrom(authentication);
}
public ExternalAuthenticationValidator getExternalAuthenticationValidator() {
return externalAuthenticationValidator;
}
public void setExternalAuthenticationValidator(ExternalAuthenticationValidator externalAuthenticationValidator) {
this.externalAuthenticationValidator = externalAuthenticationValidator;
}
}
现在,您必须使用此代码段从userDetails中检索令牌:
SecurityContext context = SecurityContextHolder.getContext();
Authentication auth = context.getAuthentication();
if(auth == null){
throw new IllegalAccessException("Authentication is null in SecurityContext");
}
if(auth instanceof UsernamePasswordAuthenticationToken){
Object details = auth.getDetails();
if(details != null && details instanceof ThirdPartyValidationResponse){
return ((ThirdPartyValidationResponse)details).getToken();
}
}
return null;
不是将它包含在您需要的任何地方,而是创建一个从身份验证的详细信息中检索它的类可能更好:
public class SecurityContextThirdPartyTokenRetriever {
public Serializable getThirdPartyToken() throws IllegalAccessException{
SecurityContext context = SecurityContextHolder.getContext();
Authentication auth = context.getAuthentication();
if(auth == null){
throw new IllegalAccessException("Authentication is null in SecurityContext");
}
if(auth instanceof UsernamePasswordAuthenticationToken){
Object details = auth.getDetails();
if(details != null && details instanceof ThirdPartyValidationResponse){
return ((ThirdPartyValidationResponse)details).getToken();
}
}
return null;
}
}
如果你选择了这种最后一种方式,只需在security xml config中声明它(或使用@Service
等注释注释):
<beans:bean id="tokenRetriever" class="com.xxx.yyy.SecurityContextThirdPartyTokenRetriever" />
还有其他方法,例如扩展UsernamePasswordAuthenticationToken以将令牌作为字段包含在其中,但这是我认为最简单的方法。