5. Spring Security¶
以下URLにアクセスすると、ログイン画面にリダイレクトされます。
→
http://localhost:8080/edu/loginForm
以下のユーザ/パスワードでログイン可能です。
管理者:01/test
一般:02/test
5.1. 設定¶
セキュリティの設定は以下のクラスになります。
SecurityConfig.java
/**
* Spring Security設定.
*/
@EnableWebSecurity // Spring Securityが提供するConfigurationクラスのインポート
// WebSecurityConfigurerAdapterでデフォルトのBean定義を適用
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/css/**").permitAll()
.antMatchers("/fonts/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/loginForm").permitAll()
// /manager配下はADMIN権限のみ(自動でROLE_が付加されROLE_ADMIN)
.antMatchers("/manager/**").hasRole("ADMIN")
// 認証していないリクエストは不許可
.anyRequest().authenticated();
// フォーム認証
http.formLogin()
// 認証パス
.loginProcessingUrl("/login")
// ログインフォーム表示用のパス設定
.loginPage("/loginForm")
// 認証失敗時のパス
.failureUrl("/loginForm?error")
// 認証成功時のパス
.defaultSuccessUrl("/", true)
// ユーザ名のリクエストパラメータ
.usernameParameter("id")
// パスワードのリクエストパラメータ
.passwordParameter("password");
http.logout()
// ログアウトURL
.logoutRequestMatcher(new AntPathRequestMatcher("/logout**"))
// ログアウト後の遷移先
.logoutSuccessUrl("/loginForm");
http.headers()
.cacheControl().disable();
}
/**
* パスワードの暗号化方式
*/
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
管理者:01/test
で管理用のURLにアクセスしてみます。
http://localhost:8080/edu/manager/news/list
マスタメンテ画面にアクセスできます。
以下のユーザでアクセスすると403エラー画面が表示されます。
一般:02/test
ログインには、対応するコントローラとテンプレートを用意します。
LoginController.java
/**
* ログイン画面のコントローラ.
*/
@Controller
public class LoginController {
@RequestMapping("loginForm")
String loginForm() {
return "loginForm";
}
}
LoginForm.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>基幹システムログイン</title>
<link rel="stylesheet" type="text/css" href="../../static/css/lib/bootstrap.min.css" th:href="@{/css/lib/bootstrap.min.css}"/>
<link rel="stylesheet" type="text/css" href="../../static/css/lib/bootstrap-theme.min.css" th:href="@{/css/lib/bootstrap-theme.min.css}"/>
</head>
<body>
<div class="container">
<h2>ログイン</h2>
<form class="form-signin" method="post" th:action="@{/login}">
<div th:if="${param.error}" class="alert alert-danger">
IDまたはパスワードが正しくありません。
</div>
<input type="text" class="form-control" name="id" placeholder="ID" required="required"
autofocus="autofocus"/>
<br/>
<input type="password" class="form-control" name="password" placeholder="Password" required="required"/>
<br/>
<button class="btn btn-lg btn-primary" type="submit">ログイン</button>
</form>
</div>
</body>
</html>
パスによるアクセス可否、ログインは上記の設定クラスで可能です。
5.2. DBとの連携¶
ユーザ/パスワードはDB管理しています。
schema.sql
-- 従業員マスタ
CREATE TABLE mst_employee
(
employee_id varchar(10) NOT NULL COMMENT '従業員番号',
employee_last_name varchar(50) COMMENT '姓',
employee_first_name varchar(50) COMMENT '名',
role_id varchar(20) NOT NULL COMMENT '権限ID',
version int COMMENT 'バージョン',
insert_user varchar(20) COMMENT '登録ユーザ',
insert_date datetime COMMENT 'insert_date',
update_user varchar(20) COMMENT '更新ユーザ',
update_date datetime COMMENT 'update_date',
PRIMARY KEY (employee_id)
) COMMENT = '従業員マスタ';
-- パスワードマスタ
CREATE TABLE mst_password
(
mst_password_id int NOT NULL COMMENT 'パスワードマスタID',
employee_id varchar(10) NOT NULL COMMENT '従業員番号',
password varchar(256) COMMENT 'パスワード',
generation varchar(2) COMMENT 'パスワード世代',
version int COMMENT 'バージョン',
insert_user varchar(20) COMMENT '登録ユーザ',
insert_date datetime COMMENT 'insert_date',
update_user varchar(20) COMMENT '更新ユーザ',
update_date datetime COMMENT 'update_date',
PRIMARY KEY (mst_password_id),
UNIQUE (employee_id, generation)
) COMMENT = 'パスワードマスタ';
-- 権限マスタ
CREATE TABLE mst_role
(
role_id varchar(20) NOT NULL COMMENT '権限ID',
role_name varchar(100) COMMENT '権限名',
version int COMMENT 'バージョン',
insert_user varchar(20) COMMENT '登録ユーザ',
insert_date datetime COMMENT 'insert_date',
update_user varchar(20) COMMENT '更新ユーザ',
update_date datetime COMMENT 'update_date',
PRIMARY KEY (role_id)
) COMMENT = '権限マスタ';
data.sql
insert into mst_employee (employee_id, employee_last_name, employee_first_name, role_id) values('01', '管理', '太郎', 'ROLE_ADMIN');
insert into mst_employee (employee_id, employee_last_name, employee_first_name, role_id) values('02', '一般', '二郎', 'ROLE_USER');
insert into mst_role (role_id, role_name) values('ROLE_ADMIN', '管理者');
insert into mst_role (role_id, role_name) values('ROLE_USER', '一般');
insert into mst_password (mst_password_id, employee_id, password) values(1, '01', '$2a$10$1gJJgBlL75OIjkSgkYPXI.mV7ihEPjxIiCkXKBEc7/r9xUIjZyc9i');
insert into mst_password (mst_password_id, employee_id, password) values(2, '02', '$2a$10$1gJJgBlL75OIjkSgkYPXI.mV7ihEPjxIiCkXKBEc7/r9xUIjZyc9i');
DB管理したユーザ情報を認証に使用する手順は以下です。
1.org.springframework.security.core.userdetails.UserDetailsService
を継承したサービスクラスを作成する。
LoginUserDetailsService.java
/**
* Spring Securityで使用するログイン時に取得するユーザ情報サービスクラス.
*/
@Service
public class LoginUserDetailsService implements UserDetailsService {
@Autowired
MstEmployeeDao dao;
@Override
public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException {
UserEntity user = dao.selectUser(id);
if (user == null) {
throw new UsernameNotFoundException("ユーザID:" + id + "は存在しません");
}
UserInfo userInfo = new UserInfo();
userInfo.setId(user.getEmployeeId());
userInfo.setEmployeeFirstName(user.getEmployeeFirstName());
userInfo.setEmployeeLastName(user.getEmployeeLastName());
userInfo.setRoleId(user.getRoleId());
userInfo.setPassword(user.getPassword());
if (userInfo == null) {
throw new UsernameNotFoundException("The requested user is not found.");
}
return new LoginUserDetails(userInfo);
}
}
loadUserByUserName
メソッドでDaoによりDBアクセスしています。
パスワードとはここでは確認せず、ユーザIDのみに合致するデータを取得しています。
SELECT
e.employee_id employee_id,
e.employee_last_name employee_last_name,
e.employee_first_name employee_first_name,
e.role_id role_id,
p.password password
FROM
mst_employee e
INNER JOIN
mst_password p
ON
e.employee_id = p.employee_id
INNER JOIN
mst_role r
ON
e.role_id = r.role_id
WHERE
e.employee_id = /* id */'01'
;
2.org.springframework.security.core.userdetails.User
を継承したユーザ情報クラスを作成する。
LoginUserDetails.java
/**
* Spring Securityで使用するログイン時に取得するユーザ情報.
*/
public class LoginUserDetails extends User {
/** ログイン情報 */
private UserInfo userInfo;
public LoginUserDetails(UserInfo userInfo) {
super(userInfo.getId(), userInfo.getPassword(), AuthorityUtils.createAuthorityList(userInfo.getRoleId()));
this.userInfo = userInfo;
}
public UserInfo getUserInfo() {
return this.userInfo;
}
}
ユーザ情報のID、パスワード、権限をコンストラクタに設定しています。 上記により、認証および認可が有効になります。
5.3. ユーザ情報取得¶
ログイン時に取得したユーザ情報は、以下で使用可能です。
コントローラクラス
/**
* お知らせ画面のコントローラ.
*/
@Controller
@RequestMapping("news")
public class NewsController {
/** ロガー */
private static final Logger logger = LoggerFactory.getLogger(NewsController.class);
/**
*
* @return
*/
@RequestMapping(method = RequestMethod.GET)
public String init(Model model, @AuthenticationPrincipal LoginUserDetails userDetails) {
model.addAttribute("loginInfo", userDetails.getUserInfo());
return "news/news";
}
}
メソッドの引数で取得可能です。
他クラス
private UserInfo getUserInfo() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null) {
return null;
}
LoginUserDetails userDetails = (LoginUserDetails) auth.getPrincipal();
if (userDetails == null) {
return null;
}
return userDetails.getUserInfo();
}
ユーザ情報はThreadLocalで保持され、SecurityContextHolder経由でアクセス可能です。
Thymeleaf
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security4">
htmlタグにxmlns:sec属性を指定する必要があります。
<a href="#" class="dropdown-toggle" data-toggle="dropdown" >
<span class="glyphicon glyphicon-user" sec:authentication="principal.userInfo.employeeName"></span> さん
<b class="caret"></b>
</a>
ユーザ情報が取得できます。
<li sec:authorize="hasRole('ADMIN')"><a th:href="@{/manager/news/list}" href="#learning"><span class="glyphicon glyphicon-pencil"></span> 重要なお知らせ管理画面</a>
権限による表示切り替えが可能です。(DBではROLE_ADMINですが、ROLE_は無視して記述します)
5.4. 参考¶
Spring Securityに限りませんがTERASOLUNAのガイドが詳細なので、参照してください。
http://terasolunaorg.github.io/guideline/5.1.0.RELEASE/ja/Security/Authentication.html