ホーム > Java > Spring Framework > Webアプリケーション > 認証、認可機能追加

認証、認可機能追加

登録機能作成(ユーザ登録)で作成したテーブル(user_info, user_role)のデータを利用して認証認可処理を追加する。

1. 認証処理(Formログイン)

  • ログイン画面を作成する。

/demo/src/main/resources/templates/login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" th:href="@{/css/common.css}">
<title>Login</title>
</head>
<body>
  <div class="header">
    <h2>ログイン</h2>
  </div>
  <div th:if="${param.error}" style="color: red;">
    ユーザ名またはパスワードが正しくありません。
  </div>
  <div th:if="${param.logout}" style="color: blue;">
    ログアウトしました。
  </div>
  <div id="content">
    <form th:action="@{/login}" method="post">
      <table>
        <tr>
          <td><label for="username">ユーザ名</label></td>
          <td><input name="username" type="text" ></td>
        </tr>
        <tr>
          <td><label for="password">パスワード</label></td>
          <td><input name="password" type="password" ></td>
        </tr>
      </table>
      <button>ログイン</button>
    </form>
  </div>
</body>
</html>

 

  • ログイン画面を表示するためのViewControllerを追加する。

/demo/src/main/java/com/ziqoo/demo/MvcConfig.java

package com.ziqoo.demo;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/loginPage").setViewName("login");
    }
}

 

  • ユーザ情報を取得するEntityクラスを作成する。user_infoとuser_roleを一度に取得するため、UserInfoを継承したクラスにUserRoleのSetをフィールドとして持たせる。

/demo/src/main/java/com/ziqoo/demo/security/dao/entity/Account.java

package com.ziqoo.demo.security.dao.entity;

import java.time.LocalDateTime;
import java.util.Set;

import org.springframework.data.relational.core.mapping.MappedCollection;
import org.springframework.data.relational.core.mapping.Table;

import com.ziqoo.demo.dao.table.entity.UserInfo;
import com.ziqoo.demo.dao.table.entity.UserRole;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@Data
@EqualsAndHashCode(callSuper = true)
@Table("user_info")
public class Account extends UserInfo {

    @MappedCollection(idColumn = "user_id")
    private Set<UserRole> roleSet;

    public Account(Integer id, String username, String password, boolean enabled, LocalDateTime createAt,
            LocalDateTime updateAt, Set<UserRole> roleSet) {
        super(id, username, password, enabled, createAt, updateAt);
        this.roleSet = roleSet;
    }
}

 

  • ユーザ情報を取得するRepositoryクラスを作成する。findByUsernameメソッドを定義する。

/demo/src/main/java/com/ziqoo/demo/security/dao/repository/AccountRepository.java

package com.ziqoo.demo.security.dao.repository;

import java.util.Optional;

import org.springframework.data.repository.CrudRepository;

import com.ziqoo.demo.security.dao.entity.Account;

public interface AccountRepository extends CrudRepository<Account, Integer> {

    Optional<Account> findByUsername(String username);

}

 

  • ログインユーザ情報を保持するUserDetailsクラスを作成する。

/demo/src/main/java/com/ziqoo/demo/security/service/AccountDetails.java

package com.ziqoo.demo.security.service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.ziqoo.demo.security.dao.entity.Account;

import lombok.Getter;

@Getter
public class AccountDetails implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private boolean enabled;
    private LocalDateTime createAt;
    private LocalDateTime updateAt;
    private List<GrantedAuthority> authorities;

    public AccountDetails(Account account) {
        this.id = account.getId();
        this.username = account.getUsername();
        this.password = account.getPassword();
        this.enabled = account.isEnabled();
        this.createAt = account.getCreateAt();
        this.updateAt = account.getUpdateAt();

        this.authorities = new ArrayList<>();

        account.getRoleSet().forEach(r -> authorities.add(new SimpleGrantedAuthority(r.getRole())));
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

}
  • ログインユーザ情報を取得するUserDetailsServiceを作成する。

/demo/src/main/java/com/ziqoo/demo/security/service/AccountDetailsService.java

package com.ziqoo.demo.security.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.ziqoo.demo.security.dao.entity.Account;
import com.ziqoo.demo.security.dao.repository.AccountRepository;

public class AccountDetailsService implements UserDetailsService {

    @Autowired
    AccountRepository accountRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Account account = accountRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("ユーザが存在しません。"));

        return new AccountDetails(account);
    }

}
  • WebSecurityConfigクラスからinMemoryAuthenticationを設定しているメソッドを削除する。
  • formLogin,logoutを以下のように設定する。
  • AccountDetailsServiceをBean登録する。

/demo/src/main/java/com/ziqoo/demo/WebSecurityConfig.java

 

@EnableWebSecurity
@Configuration
public class WebSecurityConfig {

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        // セキュリティ対象外を指定
        return web -> web.ignoring().requestMatchers(
                new AntPathRequestMatcher("/favicon.ico"),
                new AntPathRequestMatcher("/css/**"),
                new AntPathRequestMatcher("/img/**"),
                new AntPathRequestMatcher("/js/**"));
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.formLogin(login -> login
                .loginProcessingUrl("/login")
                .loginPage("/loginPage")
                .permitAll()
                .defaultSuccessUrl("/", true)
        ).logout(logout -> logout
                permitAll()
        ).authorizeHttpRequests(authz -> authz
                .requestMatchers(
                    new AntPathRequestMatcher("/"),
                    new AntPathRequestMatcher("/thSamplePage/**")).permitAll()
                .anyRequest().authenticated()
        );
        return http.build();
    }

    /**
     * パスワードエンコーダー.
     * @return PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return new AccountDetailsService();
    }
}

 

2. 認可処理

コントローラのメソッドに対して認可処理を追加する。

  • WebSecurityConfigクラスに@EnableGlobalMethodSecurity(prePostEnabled = true)を追加する。

/demo/src/main/java/com/ziqoo/demo/WebSecurityConfig.java

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  • 対象のコントローラメソッドに対し、認可するRoleを@PreAuthorize("hasAuthority('ロール文字列')")のように指定する

/demo/src/main/java/com/ziqoo/demo/web/controller/account/AccountController.java

    @PreAuthorize("hasAuthority('Admin')")    // Adminロールを持つユーザのみ実行可能
    @PostMapping("/account/register")
    public String register(@Validated AccountForm form, BindingResult bindingResult, Model model,
            RedirectAttributes ra) {

  • 複数ロールを認可する場合は@PreAuthorize("hasAnyAuthority('ロール文字列', 'ロール文字列')")のようにhasAnyAuthorityを使用する。

3. ログアウト

  • URL:/logoutに対してpostすればログアウトできる。ログアウトに成功すると/loginPage?logoutにリダイレクトされる。

実装例

    <form id="logoutForm" th:action="@{/logout}" method="post">
        <a id="logout" href="javascript:void(0);">ログアウト</a>
    </form>

<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script type="text/javascript">
$(function(){
    $("#logout").on("click", function(){
      $("#logoutForm").submit();
    });
});
</script>

 

リンク

コーポレートサイトにちょうどいいCMS、baserCMS