2016-04-05 20 views
1

私はSpring Securityも使用するSpring Bootアプリケーションを持っています。私は、ユーザーがアプリケーションにログインするためのアクセス権を持っているかどうかを確認したいが、それは認証後でなければならない。要点は、ログインするユーザーが、接続する必要のあるプロジェクトを選択することです。ユーザーは1つのプロジェクトに接続することができますが、別のプロジェクトに接続することはできません。ただし、ユーザーが無効な資格情報を入力する場合、ユーザーが選択したプロジェクトにログインする権利がない場合でも、無効な資格情報に関するメッセージが最初に表示される必要があります。このため、プロジェクトに対する権利の確認は、認証後に行う必要があります。スプリングブートセキュリティ - 認証に成功したが、認証に失敗した場合にHTTP 403を返す方法

この私のSecurityConfigクラス:UserDetailsS​​erviceの

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

} 

実装:ユーザーがログインする権利にアクセスしていない場合

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と例外メッセージを返す方法(例: "User" tural "は" AZB "プロジェクト ")、それをLoginController@ResponseStatus(HttpStatus.FORBIDDEN)または@ExceptionHandlerを使用)で処理しますか?

答えて

0

あなたが探しているものかどうかは不明ですが、コントローラのログインPOSTメソッドにHttpServletResponseを注入できます。あなたのサービスは、その認証がOKではないことを通知した場合

したがって、あなたは、例えば

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

// UPDATE:

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 

方法はorg.springframework.security.core.userdetails.UserDetailsServiceインタフェースの実装であるため、実際にあなたのようなHttpServletResponse@RequestMappingに注釈を付けます

注入することはできませんコントローラ。

しかし、ここでは別の(多分正しい!)ソリューションです:ユーザーがアクセス権を持っていない場合

1)を作成します)CustomYourException

2を投げ、loadUserByUsername(String x)メソッドの実装でこのようなメソッドが含まれていclass level@ControllerAdviceで注釈が付け新しいExceptionHandlingControllerクラス(とそれはあなたのコントローラのようにスキャンしますことを確認してください):

@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を操作し、必要なレスポンスコードを設定したり、ユーザーを特定のビューに送ることができます。

・ホープこれはあなたの問題を解決:)

+0

私は@RequestMappingのハンドラ(値= "/ログイン"、メソッド= RequestMethod.POST)を持っていません。 Spring Securityは、POSTリクエストを/ログインするために処理します。 https://spring.io/guides/gs/securing-web/ – Tural

+0

答えを新しいソリューションで更新しました。確認してください:) – niilzon

+0

私はこの問題を書く前にこの解決策を試しました。 'loadUserByUsername'はパスワードを確認する前に実行されます。この場合、ユーザーがプロジェクトへのアクセス権がなく、無効なユーザー名またはパスワードを入力した場合、ユーザーはプロジェクトにアクセスすることはできませんが、ユーザーは無効な資格情報を入力したと言います。つまり、認証の前に認証が表示されます。 – Tural

関連する問題