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.
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.
But I saw that when I implement the UserDetailsService
like below
@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
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.
It is not the responisibility of UserDetailsService
to validate the Authentication
token. This is what an AuthenticationProvider
does.
So first leave your implementation of UserDetailsService
the single responsibility of loading all the data of the user from the database by 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
.
@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
:
@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
.
<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!
PS Consider constructor injection over field injection! It's more testable and states the contract of the class better. See this discussion .
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.
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.html class provided by Spring.
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.
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
Hope this helps.
这是您的答案,您需要实现自己的过滤器并覆盖默认过滤器,以便将参数添加到登录表单。
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:
@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:
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:
@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:
@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
@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:
@RequestMapping(value = { "/login" }, method = RequestMethod.POST)
public String loginPage(@Valid @ModelAttribute("user") User user, BindingResult result, ModelMap model, HttpServletRequest request){---- call ervice methods here ----}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.