java 除了用户名和密码外,登录页面有更多字段时如何实现Spring安全?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16104228/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How implement Spring security when login page having more field apart from user name and password?
提问by Krushna
I have a login page where the user need put the below information VIN number,email, zip code and accessCode which they will get from different application.
我有一个登录页面,用户需要在其中输入以下信息 VIN 号、电子邮件、邮政编码和访问代码,他们将从不同的应用程序中获取这些信息。
So to validate a user I need all the information in my custom UserDetailsService
class and then will called a procedure to authenticate the user.
因此,为了验证用户,我需要自定义UserDetailsService
类中的所有信息,然后将调用一个过程来验证用户身份。
But I saw that when I implement the UserDetailsService
like below
但是当我实现UserDetailsService
如下所示时,我看到了
@Component
public class LoginService implements UserDetailsService {
@Autowired
LoginStoredProcedureDao loginStoredProcedureDao;
public Map<String, Object> verifyLogin(LoginDetails details) {
return loginStoredProcedureDao.verifyLogin(details);
}
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
// TODO Auto-generated method stub
//verifyLogin();
return null;
}
}
The loginDetails Object is like below
loginDetails 对象如下所示
public class LoginDetails {
String vin;
String email;
String zipcode;
String accessCode;
}
In the above situation how to use spring security. Here the user need to give all information to validate him self.
在上述情况下如何使用spring security。这里用户需要提供所有信息来验证他自己。
采纳答案by Akshay
First of all, I would solve your problem differently. I would do a multi step authentication. The first would be a traditional user name / password login, using spring security's default model. The second step would be to show another form which would have to be filled up by the user to provide additional details for authentication, which your application wants to enforce.
首先,我会以不同的方式解决您的问题。我会进行多步身份验证。第一个是传统的用户名/密码登录,使用 spring security 的默认模型。第二步是显示另一个表单,用户必须填写该表单以提供您的应用程序想要强制执行的身份验证的其他详细信息。
Regardless, if you want to continue customizing the spring security model to ask more details on login in in a single step. Follow the steps reference in the previous answer from @Petr. And then to access session attributes in your UserDetailsService class, use the http://static.springsource.org/spring/docs/2.0.8/api/org/springframework/web/context/request/RequestContextHolder.htmlclass provided by Spring.
无论如何,如果您想继续自定义 spring 安全模型以在一个步骤中询问有关登录的更多详细信息。按照@Petr 上一个答案中的步骤参考进行操作。然后要访问 UserDetailsService 类中的会话属性,请使用Spring 提供的http://static.springsource.org/spring/docs/2.0.8/api/org/springframework/web/context/request/RequestContextHolder.html类.
You can get access to currentRequestAttributes()
, which returns a RequestAttributes
object. You can query the RequestAttributes object to get the desired attribute from the desired scope.
您可以访问currentRequestAttributes()
,它返回一个RequestAttributes
对象。您可以查询 RequestAttributes 对象以从所需的范围中获取所需的属性。
Note: This is a static method, which means its not going to be friendly to unit test.
注意:这是一个静态方法,这意味着它对单元测试不友好。
You can also downcast RequestAttributes to ServletRequestAttributes
if you want to get access to the underlying HttpServletRequest
ServletRequestAttributes
如果您想访问底层,您还可以将 RequestAttributes 向下转换为HttpServletRequest
Hope this helps.
希望这可以帮助。
回答by Roadrunner
It is not the responisibility of UserDetailsService
to validate the Authentication
token. This is what an AuthenticationProvider
does.
UserDetailsService
验证Authentication
令牌不是责任。这就是 an 的AuthenticationProvider
作用。
So first leave your implementation of UserDetailsService
the single responsibility of loading all the data of the user from the database by login
:
因此,首先让您执行UserDetailsService
从数据库加载用户的所有数据的单一责任login
:
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
@Autowired
public UserDetailsServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = null;
try {
user = userRepository.findByUsername(username);
} catch (NotFoundException e) {
throw new UsernameNotFoundException(String.format("No user found for username %s!", username);
}
retrun new UserDetailsImpl(user);
}
}
Than to intercept additional parameters from a login form you need to implement AuthenticationDetailsSource
. It may be a good idea to extend WebAuthenticationDetails
, but you can have just any object returned by AuthenticationDetailsSource
.
比从您需要实现的登录表单中拦截额外的参数AuthenticationDetailsSource
。扩展可能是一个好主意WebAuthenticationDetails
,但您可以只返回任何对象AuthenticationDetailsSource
。
@Component
public class WebAuthenticationDetailsSourceImpl implements AuthenticationDetailsSource<HttpServletRequest, MyWebAuthenticationDetails> {
@Override
public MyWebAuthenticationDetails buildDetails(HttpServletRequest context) {
// the constructor of MyWebAuthenticationDetails can retrieve
// all extra parameters given on a login form from the request
// MyWebAuthenticationDetails is your LoginDetails class
return new MyWebAuthenticationDetails(context);
}
}
And to do the validation implement your own AuthenticationProvider
by either implementing the interface itself or extending AbstractUserDetailsAuthenticationProvider
or DaoAuthenticationProvider
:
并AuthenticationProvider
通过实现接口本身或扩展AbstractUserDetailsAuthenticationProvider
或来实现您自己的验证DaoAuthenticationProvider
:
@Component
public class UserDetailsAuthenticationProviderImpl extends AbstractUserDetailsAuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
MyWebAuthenticationDetails detais = (MyWebAuthenticationDetails) authentication.getDetails();
// verify the authentication details here !!!
// and return proper authentication token (see DaoAuthenticationProvider for example)
}
}
Than you just need to pass your implementations to AuthenticationManager
and UsernamePasswordAuthenticationFilter
.
比您只需要将您的实现传递给AuthenticationManager
和UsernamePasswordAuthenticationFilter
。
<util:list id="authenticationProviders">
<ref bean="userDetailsAuthenticationProviderImpl" />
</util:list>
<!--
This bean MUST have this exact ID to be the default authenticationManager!
This is required prior Spring 3.1, as authentication-manager-ref is not
present in sec:http element before!
-->
<bean id="org.springframework.security.authenticationManager"
name="authenticationManager"
class="org.springframework.security.authentication.ProviderManager"
c:providers-ref="authenticationProviders" />
<bean id="usernamePasswordAuthenticationFilter"
class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"
p:authenticationManager-ref="authenticationManager"
p:authenticationDetailsSource-ref="webAuthenticationDetailsSourceImpl" />
<sec:http authentication-manager-ref="authenticationManager">
<sec:custom-filter position="FORM_LOGIN_FILTER" ref="usernamePasswordAuthenticationFilter" />
</sec:http>
Hope this helps!
希望这可以帮助!
P.S. Consider constructor injection over field injection! It's more testable and states the contract of the class better. See this discussion.
PS 考虑构造函数注入而不是字段注入!它更具可测试性,并且更好地说明了班级的合同。请参阅此讨论。
回答by Petr Mensik
回答by vishal
Thanks. I created a custom filter class for authenticating the user based on three parameters - username, password, and account id. I autowired it as a bean in SecurityConfig class:
谢谢。我创建了一个自定义过滤器类,用于根据三个参数(用户名、密码和帐户 ID)对用户进行身份验证。我将它自动装配为 SecurityConfig 类中的 bean:
@Bean
public AccountCredentialsAuthenticationFilter accountCredentialsAuthenticationFilter()
throws Exception {
AccountCredentialsAuthenticationFilter accountCredentialsAuthenticationFilter = new AccountCredentialsAuthenticationFilter();
accountCredentialsAuthenticationFilter
.setAuthenticationManager(authenticationManagerBean());
return accountCredentialsAuthenticationFilter;
}
So, instead of just the traditional username and password fields, I was able to perform authentication using three fields (username, password, and account id) by calling appropriate service methods required for authentication and setting authorities for the logged in user:
因此,除了传统的用户名和密码字段之外,我还能够通过调用身份验证所需的适当服务方法并为登录用户设置权限,使用三个字段(用户名、密码和帐户 ID)执行身份验证:
public class AccountCredentialsAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Autowired
private UserService userService;
@Qualifier("authenticationManager")
protected AuthenticationManager authenticationManager;
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
String account = request.getParameter("account");
final String userName = request.getParameter("userName");
final String password = request.getParameter("password");
boolean isFound = userService.checkLogin(userName, password, account);
if (isFound == true) {
boolean selectedAccount = false;
UserDetails userDetails = userService.loadUserByUsername(userName);
User user = (User) userDetails;
Set<Account> accounts = user.getAccounts();
String acctSelect = null;
// user has multiple accounts
for (Account acct : accounts) {
acctSelect = acct.getAccountId().toString();
if (acctSelect.equals(account)) {
// confirm which account user has logged in with
selectedAccount = true;
account = acctSelect;
request.getSession().setAttribute("account", account);
break;
}
}
if (selectedAccount) {
Set<? extends GrantedAuthority> authorities = (HashSet<? extends GrantedAuthority>) userDetails
.getAuthorities();
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userName, password,
authorities);
token.setDetails(new WebAuthenticationDetails(request));
super.setDetails(request, token);
Authentication auth = this.getAuthenticationManager().authenticate(token);
SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(auth);
// Create a new session and add the security context.
HttpSession session = request.getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
return auth;
} else {
SecurityContextHolder.getContext().setAuthentication(null);
request.getSession().setAttribute("SPRING_SECURITY_CONTEXT", null);
throw new UsernameNotFoundException("Please input correct credentials");
}
} else {
SecurityContextHolder.getContext().setAuthentication(null);
request.getSession().setAttribute("SPRING_SECURITY_CONTEXT", null);
throw new UsernameNotFoundException("Please input correct credentials");
}
}
I overrode following methods of UsernamePasswordAuthenticationFilter class for appropriate redirection after authentication & authorization:
我重写了 UsernamePasswordAuthenticationFilter 类的以下方法,以便在身份验证和授权后进行适当的重定向:
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
redirectStrategy.sendRedirect(request, response, "/home");
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
redirectStrategy.sendRedirect(request, response, "/login?error=true");
}
I also modified the configure method in SecurityConfig class to execute the custom filter:
我还修改了 SecurityConfig 类中的 configure 方法来执行自定义过滤器:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(accountCredentialsAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()....rest of the code....}
For custom authentication in Spring Security, the method
对于 Spring Security 中的自定义身份验证,方法
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){---- call service methods here ----}
in this filter class (AccountCredentialsAuthenticationFilter) makes the following method in controller class redundant:
在此过滤器类 (AccountCredentialsAuthenticationFilter) 中,控制器类中的以下方法变得多余:
@RequestMapping(value = { "/login" }, method = RequestMethod.POST)
public String loginPage(@Valid @ModelAttribute("user") User user, BindingResult result, ModelMap model, HttpServletRequest request){---- call ervice methods here ----}