Spring Bootで認証を試す
なんか結構難しいけど
いろいろな記事見ててモヤモヤしてて他の事が手につかなくなったのでちょっとだけ試す。
認証機能
ログインやログアウトを行う機能
プロジェクト作成時の設定
pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
データベース設定
create.sql
DROP TABLE if EXISTS user; create table if not exists user ( id int not null auto_increment, username varchar(255) not null, password varchar(255) not null, primary key (id) );
data.sql
insert into user (id, username, password) values (1, 'user', 'password');
アプリケーション設定
application.properties
# server server.port=8081 # database datasource spring.datasource.url=jdbc:mysql://192.168.33.10/myapp?useSSL=false spring.datasource.username=app spring.datasource.password=Password123@ spring.datasource.driver-class-name=com.mysql.jdbc.Driver # database jpa spring.jpa.database=MYSQL spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect # default user #security.user.name=admin #security.user.password=admin
エンティティを作成
User.java
package com.example.entity; import java.util.Collection; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @Entity @Table(name = "user") public class User implements UserDetails { /** * */ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(name="username") private String username; @Column(name="password") private String password; @Override public Collection<? extends GrantedAuthority> getAuthorities() { // TODO Auto-generated method stub return null; } @Override public String getPassword() { // TODO Auto-generated method stub return this.password; } @Override public String getUsername() { // TODO Auto-generated method stub return this.username; } @Override public boolean isAccountNonExpired() { // TODO Auto-generated method stub return true; } @Override public boolean isAccountNonLocked() { // TODO Auto-generated method stub return true; } @Override public boolean isCredentialsNonExpired() { // TODO Auto-generated method stub return true; } @Override public boolean isEnabled() { // TODO Auto-generated method stub return true; } }
※認証用にUserDetailsを実装する必要があるらしい
リポジトリ
UserRepository.java
package com.example.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.example.entity.User; @Repository public interface UserRepository extends JpaRepository<User, Long>{ public User findByUsername(String username); }
※ユーザー名から取得用のメソッドを準備
サービス
UserServiceImpl.java
package com.example.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 org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import com.example.entity.User; import com.example.repository.UserRepository; @Component public class UserServiceImpl implements UserDetailsService{ @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if (StringUtils.isEmpty(username)) { throw new UsernameNotFoundException(""); } User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException(""); } return user; } }
※UserDetailsServiceを実装してユーザー情報取得用の実装を行う
認証設定
WebSecurityConfig.java
package com.example.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import com.example.handler.SampleAuthenticationFailureHandler; import com.example.service.UserServiceImpl; @Configuration @EnableWebMvcSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity httpSecurity) throws Exception { // 認可の設定 httpSecurity .authorizeRequests() .antMatchers("/login").permitAll() // /loginは認証なしでアクセス可能 .anyRequest() .authenticated(); // ログイン設定 httpSecurity .formLogin() .loginProcessingUrl("/login") // 認証処理用 .loginPage("/login") // ログインページ .failureHandler(new SampleAuthenticationFailureHandler()) // 認証失敗時のハンドラクラス .usernameParameter("username") // ユーザー名のパラメータ .passwordParameter("password") // パスワードのパラメータ .defaultSuccessUrl("/") // 認証成功時の遷移先 .and(); // ログアウト設定 httpSecurity .logout() .logoutRequestMatcher(new AntPathRequestMatcher("/logout**")) // ログアウト処理のパス .logoutSuccessUrl("/login"); // ログイン完了後のパス } @Configuration protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter { @Autowired UserServiceImpl userDetailsService; @Override public void init(AuthenticationManagerBuilder auth) throws Exception { // 認証するユーザーを設定する auth.userDetailsService(userDetailsService); } } }
SampleAuthenticationFailureHandler.java
package com.example.handler; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; public class SampleAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authenticationException) throws IOException, ServletException { String errorId = ""; // ExceptionからエラーIDをセットする if(authenticationException instanceof BadCredentialsException){ errorId = "ERROO1"; } // ログイン画面にリダイレクトする httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/login" + "?error=" + errorId); } }
画面
TopController.java
package com.example.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class TopController { @RequestMapping(value = "/", method = {RequestMethod.GET} ) public String index(){ return "index"; } }
LoginController.java
package com.example.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class LoginController { @RequestMapping(value = "/login") public String index() { return "login"; } }
SampleController.java
package com.example.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class SampleController { @RequestMapping(value = "/sample") public String index() { return "sample"; } }
index.html
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-type" content="text/html; charset=UTF-8" /> </head> <body> こんにちは! <form action="#" th:action="@{/logout}" method="POST"> <input type="submit" value="ログアウト" /> </form> </body> </html>
login.html
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>ログイン</title> <meta http-equiv="Content-type" content="text/html; charset=UTF-8" /> </head> <body> <form action="" th:action="@{/login}" method="post"> <p> ユーザーID: <input type="text" name="username" /> </p> <p> パスワード: <input type="password" name="password" /> </p> <p> <input type="submit" value="ログイン" /> </p> </form> <div th:if="${session['SPRING_SECURITY_LAST_EXCEPTION']} != null"> <span th:text="${session['SPRING_SECURITY_LAST_EXCEPTION'].message}"></span> </div> </body> </html>
sample.html
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-type" content="text/html; charset=UTF-8" /> </head> <body> こんにちは!サンプルページです <form action="#" th:action="@{/logout}" method="POST"> <input type="submit" value="ログアウト" /> </form> </body> </html>
動作サンプル
所感
認証ってセキュリティの部分なので結構深く作っているようでいろいろ混乱中です。
いろいろな人の記事みてもなんかいまいちわからないので動かしてみることにした
最初は勝手にbasic認証になっていたりと「いや、まだ使わないんですけど…」とかいう部分もあるので戸惑いながらもやってみて
なんとか認証自体は動いている状態までこぎつけた
今回はパスワードが平文のままだったりサンプルのコピペ部分もいくつかあるのでもう少しいろいろ試していかないと…