gpt4 book ai didi

java - Spring Security 自定义登录的工作原理

转载 作者:塔克拉玛干 更新时间:2023-11-01 22:40:32 26 4
gpt4 key购买 nike

我正在尝试通过 Spring Security。我必须实现自定义登录表单,因此我需要非常了解我的配置的含义。

spring-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>

登录 Controller

@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");
}
}

我知道我可以将/login URL 映射到 RequestMethod.GET,但是当我尝试在表单提交后拦截 POST 时,它不起作用。

  1. 我相信,但需要确认,那是因为 Security 在做幕后的事情:从中获取用户名和密码值发布的表格并将它们与身份验证中的表格进行比较提供者:如果匹配,则显示 default-target-url,否则显示用户必须重复登录。对吗?
  2. 那么我的问题是:我需要在安全的登录表单,因为我必须向外部服务器来验证这些是否匹配。介绍之前安全性 我使用/login GET 和/login 开发了这个机制POST,带有@ModelAttribute 注解。我现在该怎么办?
  3. 更改身份验证提供程序,使用实现 UserDetailsS​​ervice 的类,会发生什么情况?我相信,在这种情况下,登录表单中输入的用户名和密码将与从数据库中检索到的用户名和密码进行比较,因为这些已分配给用户对象。这样对吗?

UserDetailsS​​erviceImpl

    @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())));
}
}

注意用户的数据起初不在我的数据库中,那是因为我不确定 UserDetailsS​​ervice 解决方案(其中 UserDetails 仅通过用户名加载)。要检索我的客户对象,我需要用户名和密码(以发送到特定的外部 URL)然后,如果 JSON 响应是肯定的(用户名和密码正确),我必须发送另外 2 个 HTTP 请求以获取客户的数据作为名字、姓氏、国籍等。此时我的用户可以被视为已登录。

有什么建议吗?提前致谢。

最佳答案

  1. I believe, but need to confirm, that is because Security is doingsomething behind the scenes: gets username and password values fromthe posted form and compare them with the ones in the authenticationprovider: if these match, default-target-url is shown, else usermust repeat the login. Is it right?

没错。当你声明一个 <login-form>您正在配置的安全配置中的元素 UsernamePasswordAuthenticationFilter .

在那里你配置了一些 url:

  • login-page="/login": 指向 @RequestMapping 的 url返回登录表单
  • login-processing-url="/login":触发 UsernamePasswordAuthenticationFilter 的 url。这是在spring-security相当于构建一个后处理 Controller 方法。
  • default-target-url="/user":用户在提供有效用户凭据后将被重定向到的默认页面。
  • authentication-failure-url="/login":用户尝试使用无效凭据登录时将被重定向到的 url。

虽然 login-processing-url、default-target-url 和 authentication-failure-url 必须是有效的 RequestMappings,但 login-processing-url 不会到达 Spring MVC Controller 层,因为它是在到达 Spring MVC 之前执行的调度程序 servlet。

所以

@RequestMapping(value = "/login", method = RequestMethod.POST)
public ModelAndView doLogin() {
System.out.println("***LOGIN_POST***");
return new ModelAndView("users/home");
}

不会永远联系不上。

当对 /login 进行 POST 时uri,UsernamePasswordAuthenticationFilter 将执行它的 doFilter()方法来捕获用户提供的凭据,构建一个 UsernamePasswordAuthenticationToken并将其委托(delegate)给 AuthenticationManager ,这里 Authentication将在匹配AuthenticationProvider中执行.

  1. Then my problem is: I need username and password values typed in the Security's login form, because I have to send an HTTP request to an external server to verify if these match. Before to introduce Security I developed this mechanism using /login GET and /login POST, with @ModelAttribute annotation. How can I do now?I suppose when you used to perform the authentication to the external server you did it by delegating to a class from the POST /login RequestMapping.

因此,只需创建一个自定义 AuthenticationProvider,它将用户验证内容委托(delegate)给您的旧逻辑:

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>
  1. Changing the authentication-provider, using a class which implements UserDetailsService, what happens? I believe that, in this case, username and password typed in the login form are compared with the ones retrieved from the db, as these are assigned to the User object. Is it right?

正如您所说,您必须同时发送用户名和密码,我认为 UserServiceDetails 架构不符合您的要求。我认为您应该按照我在第 2 点中的建议去做。

编辑:

One last thing: now I'm sending HTTP request in authenticate method,if credentials are correct I receive a token in the response, which Ineed to get access to other external server services. How can I passit in my Spring controller?

要接收和处理收到的 token ,我会这样做:

ExternalAuthenticationValidator 接口(interface):

public interface ExternalAuthenticationValidator {

public abstract ThirdPartyValidationResponse validate(String name, String password);

}

ThirdPartyValidationResponse 模型接口(interface):

public interface ThirdPartyValidationResponse{

public boolean isValid();

public Serializable getToken();

}

然后,改变 Provider 处理和管理它的方式:

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 中检索 token :

        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;
}

}

如果您选择最后一种方式,只需在安全 xml 配置中声明它(或使用 @Service 等注释进行注释):

<beans:bean id="tokenRetriever" class="com.xxx.yyy.SecurityContextThirdPartyTokenRetriever" />

还有其他方法,例如扩展 UsernamePasswordAuthenticationToken 以将 token 作为字段包含在其中,但我认为这是最简单的方法。

关于java - Spring Security 自定义登录的工作原理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40033522/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com