Spring SecurityのhasRole(role)に要注意

# はじめに
[前回の記事](http://qiita.com/yokobonbon/items/6459bec1f09b57982bc1)の続きで、ユーザの情報をデータベースなどから取得する方法を実装しようと考えました。データベースから直接データを呼び出す、[jdbcAuthentication()](http://docs.spring.io/spring-security/site/docs/4.0.3.RELEASE/reference/html/jc.html#jc-authentication-jdbc)というメソッドもあるのですが、ユーザ認証のためにユーザ情報を取得するUserDetailsServiceを使った実装を行おうと思います。

そこで思わぬ罠にはまってしまったので共有させて頂きます。

# SecurityConfigの変更
前回のinMemoryAuthentication()を仕様している部分をUserDetailsServiceを設定するように変更します。userDetailsServiceは@Autowiredで自動的にインスタンス化されます。（もちろんそのために他でクラスを定義しておく必要があります）

```java
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        /*auth.inMemoryAuthentication()
                .withUser("user").password("password").roles(Role.USER.name())
                .and()
                .withUser("admin").password("admin").roles(Role.USER.name(), Role.ADMIN.name());*/
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin").hasRole(Role.ADMIN.name())
                .antMatchers("/").hasRole(Role.USER.name())
                .and()
                .formLogin().loginPage("/login").failureUrl("/login?error").permitAll()
                .and()
                .logout().logoutUrl("/logout").logoutSuccessUrl("/login");
    }
}
```

#UserDetailsServiceの実現
UserDetailsServiceの実現には、[loadUserByUsername(String username)](https://docs.spring.io/spring-security/site/docs/3.2.3.RELEASE/apidocs/org/springframework/security/core/userdetails/UserDetailsService.html#loadUserByUsername(java.lang.String))メソッドを実現する必要があります。つまり、ユーザネームを元にユーザ情報を返す必要があるということです。ユーザ情報はUserDetailsで返されます。

UserDetailsインターフェースは以下のようになります

```java
public interface UserDetails extends Serializable {
        Collection<? extends GrantedAuthority> getAuthorities();                                                                                                                              
        String getPassword();
        String getUsername();
        ...
```

全てを実現したクラスを作成してそれをloadUserByUsernameで返しても良いのですが、UserDetailsインターフェースを実現した[User](http://docs.spring.io/spring-security/site/docs/3.2.7.RELEASE/apidocs/org/springframework/security/core/userdetails/User.html)クラスが利用できます。今回はLoginUserというUserを継承したクラスを作成します。UserProfileはこのアプリケーション独自のユーザーの詳細情報と見て下さい。

```java
public class LoginUser extends User {
    private static final long serialVersionUID = 5039583223116504886L;

    private UserProfile userProfile;

    public LoginUser(UserProfile userProfile, String[] roles) {
        super(userProfile.getUserName(), userProfile.getPassword(),
                AuthorityUtils.createAuthorityList(roles));
        this.userProfile = userProfile;
    }

    public UserProfile getUserProfile() {
        return userProfile;
    }
}
```

```java
public class UserProfile {
    private String username;

    private String password;

    private Role[] roles;

    private String description;

    public UserProfile(String username, String password, Role[] roles, String description) {
        this.username = username;
        this.password = password;
        this.roles = roles;
        this.description = description;
    }

    public String getUserName() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public Role[] getRoles() {
        return roles;
    }

    public String getDescription() {
        return description;
    }
}
```

#hasRole(role)に注意!!
LoginUserクラスには、ロールを文字列で指定します。一見以下で良さそうです。

```java
@Override
    public UserDetails loadUserByUsername(String username) {
        . . . . 
        String[] roles = { Role.USER.name() };
        return new LoginUser(profile, roles);
    }
```

しかしこれでは動きません。HttpSecurityでは.antMatchers("/").hasRole(Role.USER.name())と
設定しているのに何故駄目なのでしょうか？

これは、hasRole()で指定した場合、"ROLE_"が自動的に付加されるためです。これに気づくまでかなり時間かかりました。。。以下のサイトでも ”分かるかいこんなん（泣）。”　と書かれています。

http://tono-n-chi.com/blog/2014/05/spring-security-role/

ROLE_を付加すれば無事動きます

```java
@Override
    public UserDetails loadUserByUsername(String username) {
        . . . . 
        String[] roles = { "ROLE_USER" };
        return new LoginUser(profile, roles);
    }
```

# ログイン後にUserDetailsの取得
ログイン後にUserDetailsの取得は、[@AuthenticaationPrincipal](https://docs.spring.io/spring-security/site/docs/current/reference/html/mvc.html#mvc-authentication-principal)を使うことで便利に取得できます。

```java
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public ModelAndView showHomePage(@AuthenticationPrincipal LoginUser user) {
        return new ModelAndView("index", "userProfile", user.getUserProfile());
    }
```

これは、以下の同じ意味になります

```java
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public ModelAndView showHomePage() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUser user = (LoginUser) authentication.getPrincipal();
        return new ModelAndView("index", "userProfile", user.getUserProfile());
    }
```