1. 概要

Webアプリケーションの一般的な要件は、ログイン後にさまざまなタイプのユーザーをさまざまなページにリダイレクトすることです。 この例としては、標準ユーザーを /homepage.html ページにリダイレクトし、管理ユーザーを/console.htmlページにリダイレクトします。

この記事では、SpringSecurityを使用してこのメカニズムを迅速かつ安全に実装する方法を示します。 この記事は、プロジェクトに必要なコアMVCのもののセットアップを扱うSpringMVCチュートリアルの上にも構築されています。

2. Springセキュリティ構成

Spring Securityは、認証が成功した後に何をするかを直接決定するコンポーネント、AuthenticationSuccessHandlerを提供します。

2.1. 基本構成

まず、基本的な@Configurationおよび@Serviceクラスを構成しましょう。

@Configuration
@EnableWebSecurity
public class SecSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            // ... endpoints
            .formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .defaultSuccessUrl("/homepage.html", true)
            // ... other configuration       
    }
}

この構成で焦点を当てる部分は、 defaultSuccessUrl()メソッドです。 ログインに成功すると、すべてのユーザーがhomepage.htmlにリダイレクトされます。

さらに、ユーザーとその役割を構成する必要があります。 この記事の目的のために、それぞれが1つの役割を持つ2人のユーザーを持つ単純なUserDetailServiceを実装します。 このトピックの詳細については、記事 Spring Security – Roles andPrivilegesをお読みください。

@Service
public class MyUserDetailsService implements UserDetailsService {

    private Map<String, User> roles = new HashMap<>();

    @PostConstruct
    public void init() {
        roles.put("admin2", new User("admin", "{noop}admin1", getAuthority("ROLE_ADMIN")));
        roles.put("user2", new User("user", "{noop}user1", getAuthority("ROLE_USER")));
    }

    @Override
    public UserDetails loadUserByUsername(String username) {
        return roles.get(username);
    }

    private List<GrantedAuthority> getAuthority(String role) {
        return Collections.singletonList(new SimpleGrantedAuthority(role));
    }
}

また、この簡単な例では、パスワードエンコーダーを使用しないため、パスワードのプレフィックスは{noop}であることに注意してください。

2.2. カスタム成功ハンドラーの追加

これで、useradminの2つの異なる役割を持つ2人のユーザーができました。 ログインに成功すると、両方がhompeage.htmlにリダイレクトされます。 ユーザーの役割に基づいて異なるリダイレクトを行う方法を見てみましょう。

まず、カスタム成功ハンドラーをBeanとして定義する必要があります。

@Bean
public AuthenticationSuccessHandler myAuthenticationSuccessHandler(){
    return new MySimpleUrlAuthenticationSuccessHandler();
}

次に、defaultSuccessUrl呼び出しをsuccessHandlerメソッドに置き換えます。このメソッドは、カスタム成功ハンドラーをパラメーターとして受け入れます。

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
        // endpoints
        .formLogin()
            .loginPage("/login.html")
            .loginProcessingUrl("/login")
            .successHandler(myAuthenticationSuccessHandler())
        // other configuration      
}

2.3. XML構成

カスタム成功ハンドラーの実装を確認する前に、同等のXML構成も確認しましょう。

<http use-expressions="true" >
    <!-- other configuration -->
    <form-login login-page='/login.html' 
      authentication-failure-url="/login.html?error=true"
      authentication-success-handler-ref="myAuthenticationSuccessHandler"/>
    <logout/>
</http>

<beans:bean id="myAuthenticationSuccessHandler"
  class="com.baeldung.security.MySimpleUrlAuthenticationSuccessHandler" />

<authentication-manager>
    <authentication-provider>
        <user-service>
            <user name="user1" password="{noop}user1Pass" authorities="ROLE_USER" />
            <user name="admin1" password="{noop}admin1Pass" authorities="ROLE_ADMIN" />
        </user-service>
    </authentication-provider>
</authentication-manager>

3. カスタム認証成功ハンドラー

AuthenticationSuccessHandler インターフェースに加えて、Springは、この戦略コンポーネントの適切なデフォルト( AbstractAuthenticationTargetUrlRequestHandler )と単純な実装( SimpleUrlAuthenticationSuccessHandler )も提供します。 通常、これらの実装はログイン後にURLを決定し、そのURLへのリダイレクトを実行します。

ある程度柔軟性はありますが、このターゲットURLを決定するメカニズムでは、プログラムで決定を行うことはできません。そのため、インターフェイスを実装し、成功ハンドラーのカスタム実装を提供します。 この実装では、ユーザーの役割に基づいて、ログイン後にユーザーをリダイレクトするURLを決定します。 

まず、onAuthenticationSuccessメソッドをオーバーライドする必要があります。

public class MySimpleUrlAuthenticationSuccessHandler
  implements AuthenticationSuccessHandler {
 
    protected Log logger = LogFactory.getLog(this.getClass());

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, 
      HttpServletResponse response, Authentication authentication)
      throws IOException {
 
        handle(request, response, authentication);
        clearAuthenticationAttributes(request);
    }

カスタマイズされたメソッドは、2つのヘルパーメソッドを呼び出します。

protected void handle(
        HttpServletRequest request,
        HttpServletResponse response, 
        Authentication authentication
) throws IOException {

    String targetUrl = determineTargetUrl(authentication);

    if (response.isCommitted()) {
        logger.debug(
                "Response has already been committed. Unable to redirect to "
                        + targetUrl);
        return;
    }

    redirectStrategy.sendRedirect(request, response, targetUrl);
}

次のメソッドが実際の作業を行い、ユーザーをターゲットURLにマップする場合:

protected String determineTargetUrl(final Authentication authentication) {

    Map<String, String> roleTargetUrlMap = new HashMap<>();
    roleTargetUrlMap.put("ROLE_USER", "/homepage.html");
    roleTargetUrlMap.put("ROLE_ADMIN", "/console.html");

    final Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
    for (final GrantedAuthority grantedAuthority : authorities) {
        String authorityName = grantedAuthority.getAuthority();
        if(roleTargetUrlMap.containsKey(authorityName)) {
            return roleTargetUrlMap.get(authorityName);
        }
    }

    throw new IllegalStateException();
}

このメソッドは、ユーザーが持つ最初の役割のマップされたURLを返すことに注意してください。 したがって、ユーザーが複数の役割を持っている場合、マップされたURLは、authoritiesコレクションで指定された最初の役割と一致するものになります。

protected void clearAuthenticationAttributes(HttpServletRequest request) {
    HttpSession session = request.getSession(false);
    if (session == null) {
        return;
    }
    session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}

戦略の中核であるdetermineTargetUrlは、ユーザーのタイプ(権限によって決定される)を確認し、このロールに基づいてターゲットURLを選択します

したがって、管理者ユーザー ROLE_ADMIN 権限によって決定されます–はログイン後にコンソールページにリダイレクトされますが、標準ユーザーによって決定されます] ROLE_USER –ホームページにリダイレクトされます。

4. 結論

いつものように、この記事で紹介するコードは、GitHubから入手できます。 これはMavenベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。