SIerだけど技術やりたいブログ

5年目のSIerのブログです

SpringSecurity 権限に基づいて認可処理をする

この記事の内容

SpringSecurityを理解すればこんな感じの、権限に基づいた制御を簡単に実現できる。

f:id:kimulla:20161020212939p:plain

f:id:kimulla:20161020212122p:plain

概要

SpringSecurityのサンプルでは、ROLEに基づいて認可してることが多い。

 @Override
  protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.authorizeRequests()
      .antMatchers("/admin/**").hasRole("ADMIN")


以下ブログが詳しいが、ロールではなく権限に基づいて制御したいときがある。
つまり、ロールごとに複数の権限を持ち、ユーザにはロールを割り当てたい場合。
権限に基づいて制御しておくと、ロールにひもづく権限を画面から変更可能にもなる。

qiita.com


そのため、権限ごとに認可制御する方法を調べた。

権限に基づいて認可処理をする方法

SpringSecurityでは認証済みユーザを
UserDetailsインタフェースを実装したクラスに保持している。

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

これとは別に、Authenticationという認証情報を保持するクラスがある。
ProvoderManagerの認証後にUserDetailsの情報から認証済みのAuthenticationが作成される。(PrincipalにはUserDetailsが入る)

public interface Authentication extends Principal, Serializable {

    Collection<? extends GrantedAuthority> getAuthorities();

    Object getCredentials();
    
    Object getDetails();
    
    Object getPrincipal();
    
    boolean isAuthenticated();
    
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}


ここらへんは前書いたブログを参照。
kimulla.hatenablog.com


このAuthenticationクラスの保持する情報をもとに、認可チェックをするためのEL式が提供されている。Spring Security Reference

  • hasRole([role])
  • hasAnyRole([role1,role2])
  • hasAuthority([authority])
  • hasAnyAuthority([authority1,authority2])

hasRoleやhasAuthorityが何をしているかというところだが、Authenticationからauthoritiesを取得して文字列変換して比較しているだけ。(authoritiesにはSimpleGrantedAuthorityが利用されることが多いが、これは本当になんもしない)
実装クラスはSecurityExpressionRoot。

hasRoleとhasAuthorityの違いは、権限の文字列に"ROLE_"プレフィックスをつけてくれるか、つけてくれないか、くらい。厳密にいうとhasRoleのプレフィックスはROLE以外も指定できるので、同じふるまいにもできる。

つまり、hasRoleとかいっておもいっきりロールを意識させられてるが、実際にはロールじゃなくていいしただのpermissionのチェックくらいの意味しかない。ひでえ。

でも権限はロールじゃないのにhasRoleを使うのもどうかと思うので、今回はhasAuthorityを使う。

ということで、UserDetailsを作る際にauthoritiesに文字列の権限情報を渡してhasAuthorityでチェックすればよい。

以下はInMemoryUserDetailsManagerを利用したサンプルだが、DBからUserDetailsを取得するときにROLEに紐づく権限を取得してauthoritiesに設定すればok

@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.authorizeRequests()
      .antMatchers("/admin/**").hasAuthority("AUTH_SHOW_ADMIN_PAGE")
      .antMatchers("/operations/create-user").hasAuthority("AUTH_CREATE_USER")
      .antMatchers("/operations/show-all").hasAuthority("AUTH_SHOW_ALL")
      .anyRequest().authenticated()
    .and()
    .formLogin()
      .loginPage("/login")
      .defaultSuccessUrl("/home")
      .failureUrl("/login?error")
      .permitAll().and()
    .logout()
      .logoutUrl("/logout")
      .logoutSuccessUrl("/login?logout")
      .permitAll();
  }
  
  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
      .withUser("user").password("password")
      .authorities(new String[]{"AUTH_SHOW_ALL", "AUTH_CREATE_USER","AUTH_SHOW_USER_PAGE"})
    .and()
      .withUser("admin").password("password")
      .authorities(new String[]{"AUTH_SHOW_ADMIN_PAGE"});
  }
}

サンプル書いた

10/19 画面からロールにひもづく権限を更新できるように改良した。
https://github.com/kimullamen/springsecurity-with-boot-authority

f:id:kimulla:20161020212939p:plain

f:id:kimulla:20161020212122p:plain