我的Spring Boot应用程序也使用Spring Security。我想检查用户是否有权登录应用程序,但必须在身份验证之后。重点是,登录用户选择他/她必须连接的项目。可以允许用户连接到一个项目,但不允许用户连接到另一个项目。但是,如果用户将输入无效凭据,则必须首先显示有关无效凭据的消息,即使用户无权登录所选项目也是如此。因此,检查项目的权限必须在身份验证之后。
这是我的SecurityConfig类:
package org.aze.accountingprogram.config;
import org.aze.accountingprogram.models.CurrentUser;
import org.aze.accountingprogram.models.PermissionAliasConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/lib/**").permitAll().anyRequest().fullyAuthenticated()
.and()
.formLogin().successHandler(successHandler()).loginPage("/login").permitAll()
.and().exceptionHandling().accessDeniedHandler(accessDeniedHandler())
.and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/login").permitAll();
http.csrf().disable();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new Md5PasswordEncoder());
}
private AccessDeniedHandler accessDeniedHandler() {
return (request, response, e) -> {
logger.debug("Returning HTTP 403 FORBIDDEN with message: \"{}\"", e.getMessage());
response.sendError(HttpStatus.FORBIDDEN.value(), e.getMessage());
};
}
private AuthenticationSuccessHandler successHandler() {
return new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
CurrentUser user = (CurrentUser) authentication.getPrincipal();
if (!user.hasAccessRight(PermissionAliasConstants.LOGIN)) {
throw new AccessDeniedException(String.format("User \"%s\" is not authorized to login \"%s\" project", user.getUsername(), user.getProject().getName()));
}
}
};
}
}
UserDetailsService的实现:
package org.aze.accountingprogram.serviceimpl;
import org.aze.accountingprogram.common.Constants;
import org.aze.accountingprogram.exceptions.DataNotFoundException;
import org.aze.accountingprogram.models.AccessRightsPermission;
import org.aze.accountingprogram.models.CurrentUser;
import org.aze.accountingprogram.models.Project;
import org.aze.accountingprogram.models.User;
import org.aze.accountingprogram.service.AccessRightsService;
import org.aze.accountingprogram.service.ProjectService;
import org.aze.accountingprogram.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private ProjectService projectService;
@Autowired
private AccessRightsService accessRightsService;
@Autowired
private HttpServletRequest request;
private static final Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user;
Project project;
final String projectId = request.getParameter(Constants.SESSION_PROJECT_ID);
logger.debug("Username: {}, projectId: {}", username, projectId);
try {
user = userService.getUserByUsername(username);
} catch (DataNotFoundException e) {
throw new UsernameNotFoundException(e.getMessage(), e);
}
// Value of projectId is from combo box which is filled from table of projects
// That is why there is no probability that the project will not be found
project = projectService.getProjectById(Integer.valueOf(projectId));
// User can have different rights for different projects
List<AccessRightsPermission> accessRights = accessRightsService.getAccessRightsByProject(user.getId(), project.getId());
Set<GrantedAuthority> authorities = new HashSet<>(accessRights.size());
authorities.addAll(accessRights.stream().map(right -> new SimpleGrantedAuthority(right.getAlias())).collect(Collectors.toList()));
final CurrentUser currentUser = new CurrentUser(user, project, authorities);
// If to check LOGIN access right to project here, and user entered right credentials
// then user will see message about invalid credentials.
// .and().exceptionHandling().accessDeniedHandler(accessDeniedHandler()) at SecurityConfig is not working in this case
// if (!currentUser.hasAccessRight(PermissionAliasConstants.LOGIN)) {
// throw new AccessDeniedException(String.format("User \"%s\" is not authorized to login \"%s\" project", user.getUsername(), project.getName()));
// }
logger.info("Logged in user: {}", currentUser);
return currentUser;
}
}
和LoginController
package org.aze.accountingprogram.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import java.util.Optional;
@Controller
public class LoginController {
@RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView getLoginPage(@RequestParam Optional<String> error) {
return new ModelAndView("login", "error", error);
}
}
如果用户无权登录项目, successHandler()
可以正常工作,应用程序会抛出AccessDeniedException
。但accessDeniedHandler()
无法正常工作,也无法发送HTTP 403.相反,我收到的是HTTP 500.
如何使用HTTP 403和异常消息返回响应(例如&#34;用户&#34; tural&#34;无权登录&#34; AZB&#34;项目&#34;)并处理它在LoginController
中(使用@ResponseStatus(HttpStatus.FORBIDDEN)
或@ExceptionHandler
)?
答案 0 :(得分:0)
不确定这是否是您要查找的内容,但您可以在Controller的登录POST方法中注入HttpServletResponse
。
因此,如果您的服务通知您授权不正常,您可以执行
response.setStatus(403);
@RequestMapping(value = "/login", method = RequestMethod.POST)
public ModelAndView loginAction(@RequestParam String login, @RequestParam String pw, HttpServletResponse response) {
doStuff(login,pw);
if(service.isNotHappy(login,pw)){
response.setStatus(403);
}else{
// happy flow
}
...
}
//更新:
以后
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
方法是org.springframework.security.core.userdetails.UserDetailsService
接口的实现,实际上你不能像HttpServletResponse
带注释的控制器一样注入@RequestMapping
。
然而,这是另一个(希望是正确的!)解决方案:
1)在loadUserByUsername(String x)
方法的实现中,如果用户没有访问权限,则抛出CustomYourException
2)创建一个新的ExceptionHandlingController
类,在@ControllerAdvice
注释class level
(并确保它像控制器一样被扫描),其中包含如下方法:
@ControllerAdvice
public class ExceptionHandlingController {
@ExceptionHandler({CustomYourException.class})
public ModelAndView handleCustomExceptionError(HttpServletRequest request, HttpServletResponse response, CustomYourException cyee) {
// this method will be triggered upon CustomYourException only
// you can manipulate the response code here,
// return a custom view, use a special logger
// or do whatever else you want here :)
}
}
如您所见,此处理程序将根据您在@ExceptionHandler
注释中定义的特定异常进行调用 - 非常有用!
从那里,您可以操纵HttpServletResponse并设置所需的响应代码,和/或将用户发送到特定视图。
希望这能解决您的问题:)