Java Spring Security 3.2.1 具有不同 WebSecurityConfigurerAdapters 的多个登录表单
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22845474/
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
Spring Security 3.2.1 Multiple login forms with distinct WebSecurityConfigurerAdapters
提问by Magnus Smith
I'm using Spring Security 3.2.1.RELEASE with Spring MVC 4.0.4.RELEASE
我正在使用 Spring Security 3.2.1.RELEASE 和 Spring MVC 4.0.4.RELEASE
I'm trying to setup Spring Security for a web application that will have two distinct login entry pages. I need the pages to be distinct as they will be styled and accessed differently.
我正在尝试为具有两个不同登录条目页面的 Web 应用程序设置 Spring Security。我需要不同的页面,因为它们的样式和访问方式不同。
First login page is for Admin users and protects admin pages /admin/**
第一个登录页面用于管理员用户并保护管理页面 /admin/**
Second login page is for Customer users and protects customer pages /customer/**.
第二个登录页面用于客户用户并保护客户页面 /customer/**。
I've attempted to setup two subclasses of WebSecurityConfigurerAdapter configuring individual HttpSecurity objects.
我试图设置 WebSecurityConfigurerAdapter 的两个子类来配置单个 HttpSecurity 对象。
CustomerFormLoginWebSecurity is protecting customer pages and redirecting to customer login page if not authorised. The AdminFormLoginWebSecurity is protecting admin pages redirecting to admin login page if not authorised.
CustomerFormLoginWebSecurity 保护客户页面并在未经授权的情况下重定向到客户登录页面。如果未经授权,AdminFormLoginWebSecurity 正在保护管理页面重定向到管理登录页面。
Unfortunately it seems that only the first of the configurations is enforced. I think that I am missing something extra to make these both work.
不幸的是,似乎只有第一个配置被强制执行。我认为我缺少一些额外的东西来使这两者都起作用。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("customer").password("password").roles("CUSTOMER").and()
.withUser("admin").password("password").roles("ADMIN");
}
@Configuration
@Order(1)
public static class CustomerFormLoginWebSecurity extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/", "/signin/**", "/error/**", "/templates/**", "/resources/**", "/webjars/**");
}
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/customer/**").hasRole("CUSTOMER")
.and()
.formLogin()
.loginPage("/customer_signin")
.failureUrl("/customer_signin?error=1")
.defaultSuccessUrl("/customer/home")
.loginProcessingUrl("/j_spring_security_check")
.usernameParameter("j_username").passwordParameter("j_password")
.and()
.logout()
.permitAll();
http.exceptionHandling().accessDeniedPage("/customer_signin");
}
}
@Configuration
public static class AdminFormLoginWebSecurity extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/", "/signin/**", "/error/**", "/templates/**", "/resources/**", "/webjars/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/admin_signin")
.failureUrl("/admin_signin?error=1")
.defaultSuccessUrl("/admin/home")
.loginProcessingUrl("/j_spring_security_check")
.usernameParameter("j_username").passwordParameter("j_password")
.and()
.logout()
.permitAll();
http.exceptionHandling().accessDeniedPage("/admin_signin");
}
}
}
采纳答案by Magnus Smith
The solution that I have come to for multiple login pages involves a single http authentication but I provide my own implementations of
我为多个登录页面提供的解决方案涉及单个 http 身份验证,但我提供了自己的实现
AuthenticationEntryPoint
AuthenticationFailureHandler
LogoutSuccessHandler
AuthenticationEntryPoint
AuthenticationFailureHandler
LogoutSuccessHandler
What I needed was for these implementations to be able to switch dependent on a token in the request path.
我需要的是这些实现能够根据请求路径中的令牌进行切换。
In my website the pages with a customer token in the url are protected and require a user to authenticate as CUSTOMER at the customer_signin page.
So if wanted to goto a page /customer/home then I need to be redirected to the customer_signin page to authenticate first.
If I fail to authenticate on customer_signin then I should be returned to the customer_signin with an error paramater. So that a message can be displayed.
When I am successfully authenticated as a CUSTOMER and then wish to logout then the LogoutSuccessHandler should take me back to the customer_signin page.
在我的网站中,url 中带有客户令牌的页面受到保护,并要求用户在 customer_signin 页面上以 CUSTOMER 身份进行身份验证。因此,如果想转到 /customer/home 页面,那么我需要首先重定向到 customer_signin 页面进行身份验证。如果我无法在 customer_signin 上进行身份验证,那么我应该返回到 customer_signin 并带有错误参数。以便可以显示消息。
当我成功通过 CUSTOMER 身份验证然后希望注销时,LogoutSuccessHandler 应将我带回 customer_signin 页面。
I have a similar requirement for admins needing to authenticate at the admin_signin page to access a page with an admin token in the url.
我对需要在 admin_signin 页面进行身份验证以访问 url 中带有管理令牌的页面的管理员有类似的要求。
First I defined a class that would allow me to take a list of tokens (one for each type of login page)
首先,我定义了一个类,它允许我获取令牌列表(每种类型的登录页面一个)
public class PathTokens {
private final List<String> tokens = new ArrayList<>();
public PathTokens(){};
public PathTokens(final List<String> tokens) {
this.tokens.addAll(tokens);
}
public boolean isTokenInPath(String path) {
if (path != null) {
for (String s : tokens) {
if (path.contains(s)) {
return true;
}
}
}
return false;
}
public String getTokenFromPath(String path) {
if (path != null) {
for (String s : tokens) {
if (path.contains(s)) {
return s;
}
}
}
return null;
}
public List<String> getTokens() {
return tokens;
}
}
I then use this in PathLoginAuthenticationEntryPoint
to change the login url depending on the token in the request uri.
然后我使用它PathLoginAuthenticationEntryPoint
来根据请求 uri 中的令牌更改登录 url。
@Component
public class PathLoginAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
private final PathTokens tokens;
@Autowired
public PathLoginAuthenticationEntryPoint(PathTokens tokens) {
// LoginUrlAuthenticationEntryPoint requires a default
super("/");
this.tokens = tokens;
}
/**
* @param request the request
* @param response the response
* @param exception the exception
* @return the URL (cannot be null or empty; defaults to {@link #getLoginFormUrl()})
*/
@Override
protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) {
return getLoginUrlFromPath(request);
}
private String getLoginUrlFromPath(HttpServletRequest request) {
String requestUrl = request.getRequestURI();
if (tokens.isTokenInPath(requestUrl)) {
return "/" + tokens.getTokenFromPath(requestUrl) + "_signin";
}
throw new PathTokenNotFoundException("Token not found in request URL " + requestUrl + " when retrieving LoginUrl for login form");
}
}
PathTokenNotFoundException extends AuthenticationException so that you can handle it in the usual way.
PathTokenNotFoundException 扩展了 AuthenticationException 以便您可以以通常的方式处理它。
public class PathTokenNotFoundException extends AuthenticationException {
public PathTokenNotFoundException(String msg) {
super(msg);
}
public PathTokenNotFoundException(String msg, Throwable t) {
super(msg, t);
}
}
Next I provide an implementation of AuthenticationFailureHandler
that looks at the referer url in the request header to determine which login error page to direct the user to.
接下来,我提供了一个实现,AuthenticationFailureHandler
它查看请求标头中的引用 URL,以确定将用户定向到哪个登录错误页面。
@Component
public class PathUrlAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private final PathTokens tokens;
@Autowired
public PathUrlAuthenticationFailureHandler(PathTokens tokens) {
super();
this.tokens = tokens;
}
/**
* Performs the redirect or forward to the {@code defaultFailureUrl associated with this path} if set, otherwise returns a 401 error code.
* <p/>
* If redirecting or forwarding, {@code saveException} will be called to cache the exception for use in
* the target view.
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
setDefaultFailureUrl(getFailureUrlFromPath(request));
super.onAuthenticationFailure(request, response, exception);
}
private String getFailureUrlFromPath(HttpServletRequest request) {
String refererUrl = request.getHeader("Referer");
if (tokens.isTokenInPath(refererUrl)) {
return "/" + tokens.getTokenFromPath(refererUrl) + "_signin?error=1";
}
throw new PathTokenNotFoundException("Token not found in referer URL " + refererUrl + " when retrieving failureUrl for login form");
}
}
Next I provide an implementation of LogoutSuccessHandler
that will logout the user and redirect them to the correct signin page depending on the token in ther referer url in the request header.
接下来,我提供了一个实现,LogoutSuccessHandler
该实现将根据请求标头中的引用 URL 中的令牌将用户注销并将其重定向到正确的登录页面。
@Component
public class PathUrlLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
private final PathTokens tokens;
@Autowired
public PathUrlLogoutSuccessHandler(PathTokens tokens) {
super();
this.tokens = tokens;
}
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
setDefaultTargetUrl(getTargetUrlFromPath(request));
setAlwaysUseDefaultTargetUrl(true);
handle(request, response, authentication);
}
private String getTargetUrlFromPath(HttpServletRequest request) {
String refererUrl = request.getHeader("Referer");
if (tokens.isTokenInPath(refererUrl)) {
return "/" + tokens.getTokenFromPath(refererUrl) + "_signin";
}
throw new PathTokenNotFoundException("Token not found in referer URL " + refererUrl + " when retrieving logoutUrl.");
}
}
The final step is to wire them all together in the security configuration.
最后一步是在安全配置中将它们连接在一起。
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired PathLoginAuthenticationEntryPoint loginEntryPoint;
@Autowired PathUrlAuthenticationFailureHandler loginFailureHandler;
@Autowired
PathUrlLogoutSuccessHandler logoutSuccessHandler;
@Bean
public PathTokens pathTokens(){
return new PathTokens(Arrays.asList("customer", "admin"));
}
@Autowired
public void registerGlobalAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("customer").password("password").roles("CUSTOMER").and()
.withUser("admin").password("password").roles("ADMIN");
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/", "/signin/**", "/error/**", "/templates/**", "/resources/**", "/webjars/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http .csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/customer/**").hasRole("CUSTOMER")
.and()
.formLogin()
.loginProcessingUrl("/j_spring_security_check")
.usernameParameter("j_username").passwordParameter("j_password")
.failureHandler(loginFailureHandler);
http.logout().logoutSuccessHandler(logoutSuccessHandler);
http.exceptionHandling().authenticationEntryPoint(loginEntryPoint);
http.exceptionHandling().accessDeniedPage("/accessDenied");
}
}
Once you have this configured you need a controller to to direct to the actual signin page. The SigninControiller below checks the queryString for a value that would indicate a signin error and then sets an attribute used to control an error message.
完成此配置后,您需要一个控制器来定向到实际登录页面。下面的 SigninControiller 检查 queryString 的值,该值将指示登录错误,然后设置用于控制错误消息的属性。
@Controller
@SessionAttributes("userRoles")
public class SigninController {
@RequestMapping(value = "customer_signin", method = RequestMethod.GET)
public String customerSignin(Model model, HttpServletRequest request) {
Set<String> userRoles = AuthorityUtils.authorityListToSet(SecurityContextHolder.getContext().getAuthentication().getAuthorities());
model.addAttribute("userRole", userRoles);
if(request.getQueryString() != null){
model.addAttribute("error", "1");
}
return "signin/customer_signin";
}
@RequestMapping(value = "admin_signin", method = RequestMethod.GET)
public String adminSignin(Model model, HttpServletRequest request) {
Set<String> userRoles = AuthorityUtils.authorityListToSet(SecurityContextHolder.getContext().getAuthentication().getAuthorities());
model.addAttribute("userRole", userRoles);
if(request.getQueryString() != null){
model.addAttribute("error", "1");
}
return "signin/admin_signin";
}
}
回答by Nico
Maybe this post could help you : Multiple login forms
也许这篇文章可以帮助您: 多个登录表单
It's a different version of spring security but the same problem : only the first configuration is taken.
这是 spring security 的不同版本,但存在相同的问题:仅采用第一个配置。
It seems it has been solved by changing login-processing-url for one of the two login pages but people suggest to use the same url processing but a different layout using ViewResolver. It is a solution if you use the same mechanism to authenticate users (the authentication mechanism is the thing responsible for processing the credentials that the browser is sending).
似乎已经通过更改两个登录页面之一的 login-processing-url 解决了这个问题,但人们建议使用相同的 url 处理,但使用 ViewResolver 使用不同的布局。如果您使用相同的机制对用户进行身份验证(身份验证机制负责处理浏览器发送的凭据),这是一种解决方案。
This post also seems to say that if you change your loginProcessingUrl you will succeed : Configuring Spring Security 3.x to have multiple entry points
这篇文章似乎还说,如果您更改 loginProcessingUrl,您将成功:将 Spring Security 3.x 配置为具有多个入口点
回答by Angular University
The component of the spring login chain that redirects to a login page is the authentication filter, and the filter that get's plugged in when using http.formLogin()
is DefaultLoginPageGeneratingFilter
.
重定向到登录页面的 spring 登录链的组件是身份验证过滤器,使用时插入的过滤器http.formLogin()
是DefaultLoginPageGeneratingFilter
.
This filter either redirects to the login url or builds a default basic login page, if no login page url is provided.
如果未提供登录页面 url,此过滤器要么重定向到登录 url,要么构建一个默认的基本登录页面。
What you need then is a custom authentication filter with the logic to define which login page is needed, and then plug it in the spring security chain in place of the single page authentication filter.
那么您需要的是一个自定义身份验证过滤器,它具有定义需要哪个登录页面的逻辑,然后将其插入 spring 安全链以代替单页身份验证过滤器。
Consider creating a TwoPageLoginAuthenticationFilter
by subclassing DefaultLoginPageGeneratingFilter
and overriding getLoginPageUrl()
, and if that is not sufficient then copy the code and modify it to meet your needs.
考虑TwoPageLoginAuthenticationFilter
通过子类化DefaultLoginPageGeneratingFilter
和覆盖来创建getLoginPageUrl()
,如果这还不够,则复制代码并修改它以满足您的需要。
This filter is a GenericFilterBean
, so you can declare it like this:
这个过滤器是一个GenericFilterBean
,所以你可以像这样声明它:
@Bean
public Filter twoPageLoginAuthenticationFilter() {
return new TwoPageLoginAuthenticationFilter();
}
then try building only one http configuration and don't set formLogin()
, but instead do:
然后尝试只构建一个 http 配置,不要设置formLogin()
,而是执行:
http.addFilterBefore(twoPageLoginAuthenticationFilter, ConcurrentSessionFilter.class);
and this will plug the two form authentication filter in the right place in the chain.
这会将两个表单身份验证过滤器插入链中的正确位置。
回答by Jen Sze
I also encountered this problem and found out that I missed the first filtering part.
我也遇到了这个问题,发现漏掉了第一个过滤部分。
This one:
这个:
http.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
Should be:
应该:
http.csrf().disable()
.antMatcher("/admin/**")
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
Adding the first filtering .antMatcher("/admin/**") will first filter it so that it will use the AdminFormLoginWebSecurity instead of the other one.
添加第一个过滤 .antMatcher("/admin/**") 将首先过滤它,以便它使用 AdminFormLoginWebSecurity 而不是另一个。