m_shige1979のときどきITブログ

プログラムの勉強をしながら学習したことや経験したことをぼそぼそと書いていきます

Github(変なおっさんの顔でるので気をつけてね)

https://github.com/mshige1979

Spring Bootで独自の認証を組み込む

認証を独自に実装したい

DBの認証やアルゴリズムがちょっと特殊な場合など

今回やったこと

・AuthenticationProviderを実装したAuthenticationProviderImplクラスを作成してここで認証を実施
・WebSecurityConfigurerAdapterにAuthenticationProviderImplを設定する

独自認証実装

AuthenticationProviderImpl.java
package com.example.config;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

import com.example.entity.User;

@Component
public class AuthenticationProviderImpl implements AuthenticationProvider {
	
	private static final Logger logger = LoggerFactory.getLogger(AuthenticationProviderImpl.class);
	
	@Override
	public Authentication authenticate(Authentication auth) throws AuthenticationException {
		
		// ユーザーとパスワードを取得
		String id = auth.getName();
        String password = auth.getCredentials().toString();
		
        // 未設定の場合はエラー
        if ("".equals(id) || "".equals(password))  {
            // 例外はSpringSecurityにあったものを適当に使用
            throw new AuthenticationCredentialsNotFoundException("ログイン情報に不備があります。");
        }
        
        // 認証情報を取得
        User user = authCheck(id, password);
        if (user == null) {
        	throw new AuthenticationCredentialsNotFoundException("ログイン情報が存在しません。");
        }
		// トークンを返却
		return new UsernamePasswordAuthenticationToken(user, password, auth.getAuthorities());
	}

	@Override
	public boolean supports(Class<?> token) {
		return UsernamePasswordAuthenticationToken.class.isAssignableFrom(token);
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private User authCheck(String username, String password) {
		User user = null;
		
		List<Map<String, String>> list = new ArrayList<>();
		Map map;
		map = new HashMap<String, String>(){
			private static final long serialVersionUID = 1L;
			{put("username", "admin");}
			{put("password", "password");}
			{put("role", "ADMIN");}
		};
		list.add(map);
		map = new HashMap<String, String>(){
			private static final long serialVersionUID = 1L;
			{put("username", "user");}
			{put("password", "password");}
			{put("role", "USER");}
		};
		list.add(map);
		map = new HashMap<String, String>(){
			private static final long serialVersionUID = 1L;
			{put("username", "hogehoge");}
			{put("password", "password2");}
			{put("role", "USER");}
		};
		list.add(map);
		
		for(Map map1 : list) {
			if (map1.get("username").equals(username) && map1.get("password").equals(password)){
				user = new User();
				user.setUsername(map1.get("username").toString());
				user.setPassword(map1.get("password").toString());
				user.setRole(map1.get("role").toString());
				break;
			}
		}
		
		return user;
	}

}

※独自実装対応

セキュリティ設定

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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.example.handler.SampleAuthenticationFailureHandler;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Override
    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
		// 認可の設定
        httpSecurity
        	.authorizeRequests()
        	.antMatchers("/css/**", "/img/**", "/js/**", "/login").permitAll()	// /loginは認証なしでアクセス可能
        	.antMatchers("/admin/**").hasRole("ADMIN")	// ADMINユーザーのみアクセス可能
        	.antMatchers("/user/**").hasRole("USER")	// USERユーザーのみアクセス可能
        	.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
        private AuthenticationProviderImpl authenticationProvider;
        
        @Override
        public void init(AuthenticationManagerBuilder auth) throws Exception {
        	// 認証方法を設定する
        	auth.authenticationProvider(authenticationProvider);
        }
    }
	
}

※authenticationProviderで認証設定を行う

エンティティ

User.java
package com.example.entity;

import java.util.ArrayList;
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.data.annotation.Transient;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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;
	
	@Column(name="role")
	private String role;
	
	public void setUsername(String username){
		this.username = username;
	}
	
	public void setPassword(String password){
		this.password = password;
	}
	
	public String getRole() {
		return this.role;
	}
	
	public void setRole(String role){
		this.role = role;
	}
	
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		// TODO Auto-generated method stub
		Collection<GrantedAuthority> authorityList = new ArrayList<>();
		authorityList.add(new SimpleGrantedAuthority("ROLE_" + this.role));
		return authorityList;
	}

	@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;
	}

}

※user情報設定用にsetterを付与

所感

これでなんとかDB以外のアクセスも対応できそう。
つかうかどうかはまだ不明ですがね…

Spring Bootで独自のエラー画面を表示したい

エラー画面がほしい

システムの画面はちょっと困る

やること

・EmbeddedServletContainerCustomizerを実装する
・ErrorControllerクラスを作成する
・Viewを作成する

EmbeddedServletContainerCustomizer実装

Customizer.java
package com.example.error;

import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

@Component
public class Customizer implements EmbeddedServletContainerCustomizer {

	@Override
	public void customize(ConfigurableEmbeddedServletContainer container) {
		container.addErrorPages(new ErrorPage(HttpStatus.FORBIDDEN, "/403"));
		container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404"));
	}

}

※@Componentを付与して対応

エラー用

ErrorController.java
package com.example.controller.error;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.http.HttpStatus;

@Controller
public class ErrorController {
	
	@RequestMapping(value = "/403", method = RequestMethod.GET)
	@ResponseStatus(HttpStatus.NOT_FOUND)
	public String forbidden() {
		return "errorpage/403";
	}
	
	@RequestMapping(value = "/404", method = RequestMethod.GET)
	@ResponseStatus(HttpStatus.NOT_FOUND)
	public String notFound() {
		return "errorpage/404";
	}
	
}

※エラーページを指定する

エラー画面

errorpage/403.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
</head>
<body>
	403 Forbidden
</body>
</html>
errorpage/404.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
</head>
<body>
	404 Not Found
</body>
</html>


単純にtemplates配下にerrorディレクトリ作成するだけでも対応できるかも…

画面

f:id:m_shige1979:20161227005540p:plain

Spring Bootでセキュリティに権限を設定する

認可

ユーザーに特定の条件を付与してアクセス権限みたいなものを設定する
今回は1つの権限で1つのURLへのアクセスを制御する。
実際はロールとかいうもので1ユーザーが複数持てるように管理すべきだが面倒なんで…

やること

ユーザーテーブルに権限のエリアを追加
WebSecurityConfigurerAdapterの派生クラスを修正
ユーザーのエンティティクラスで権限を設定するように対応

テーブル

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,
   role        varchar(255) not null,
   primary key (id)
);

※roleを追加

セキュリティ設定

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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.example.handler.SampleAuthenticationFailureHandler;
import com.example.service.UserServiceImpl;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Override
    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
		// 認可の設定
        httpSecurity
        	.authorizeRequests()
        	.antMatchers("/css/**", "/img/**", "/js/**", "/login").permitAll()	// /loginは認証なしでアクセス可能
        	.antMatchers("/admin/**").hasRole("ADMIN")	// ADMINユーザーのみアクセス可能
        	.antMatchers("/user/**").hasRole("USER")	// USERユーザーのみアクセス可能
        	.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);
        }
    }
	
}

エンティティ

User.java
package com.example.entity;

import java.util.ArrayList;
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.data.annotation.Transient;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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;
	
	@Column(name="role")
	private String role;
	
	public String getRole() {
		return this.role;
	}
	
	public void setRole(String role){
		this.role = role;
	}
	
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		// TODO Auto-generated method stub
		Collection<GrantedAuthority> authorityList = new ArrayList<>();
		authorityList.add(new SimpleGrantedAuthority("ROLE_" + this.role));
		return authorityList;
	}

	@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を実装したgetAuthoritiesメソッドでROLEを生成して設定するように対応
※ROLE_XXXのような形で設定しておく必要があります。

動作

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認証になっていたりと「いや、まだ使わないんですけど…」とかいう部分もあるので戸惑いながらもやってみて
なんとか認証自体は動いている状態までこぎつけた
今回はパスワードが平文のままだったりサンプルのコピペ部分もいくつかあるのでもう少しいろいろ試していかないと…

doma2のsql文調査

SQL文の複合条件とか気になるので調べる

SELECT、INSERT、UPDATEらへんの挙動

SELECT

基本的にはSQLを作らなければならない

基本形
@Select
List<Item> findAll();

※@Selectアノテーションを付けたものが対象となります。

対応するSQL

select
    /*%expand*/*
from
    item
order by 
	id asc

SQLバインド変数が存在しないので直接設定する。

条件あり
@Select
Item findCondOne(Integer id);

※引数を設定することでsqlバインド変数を設定できる

select
    /*%expand*/*
from
    item 
where
	id = /* id */1
	
	
可変条件
@Select
List<Item> findCondAll(Integer id, String name, Integer price);

select
    /*%expand*/*
from
    item 

    where
/*%if id != null || name != null || price != null */
	/*%if id != null */
		id = /* id */1
	/*%end*/
	
	/*%if name != null */
		and
		name = /* name */'a'
	/*%end*/ 
	
	/*%if price != null */
		and
		price = /* price */1
	/*%end*/
/*%end*/

	
order by 
	id asc

※複数の条件を任意に条件に設定する場合はちょっと工夫が必要

INSERT

SQLを必要とする場合と必要ない場合がある。

基本形
@Insert
int insert(Item entity);

SQLは不要

SQLを作成する
@Insert(sqlFile = true)
int insertExecute(Item entity);

アノテーションにsqlFile = trueを付与

insert into item (name, price, create_at, update_at)
values (/* entity.getName() */'a',
        /* entity.getPrice() */100,
        /* entity.getCreateAt() */'2016-12-20T11:22:33',
        /* entity.getUpdateAt() */'2016-12-20T11:22:33')

バインド変数を設定する

UPDATE

SQLを必要とする場合と必要ない場合がある。

基本形
@Update
int update(Item entity);

SQLは不要

SQLを作成する
@Update(sqlFile = true)
int updateExecute(Item entity, String update);

update 
	item 
set 
	name = /* entity.getName() */'a',
	price = /* entity.getPrice() */1,
	update_at = /* entity.getUpdateAt() */'2016-12-24 02:56:55.33'
where
	id = /* entity.getId() */1
	and update_at = /* update */'2016-12-24 02:56:55.33'

バインド変数を設定する


今回はここまで

doma2によるデータの抽出を試す

この前は自動生成しかしていないので

自動生成なしで準備して見る

環境

Mac
eclipse

ライブラリ

doma2
mysql

準備

Javaプロジェクトを準備し、ライブラリを設定しておく

f:id:m_shige1979:20161224014909p:plain

プロジェクトのプロパティより注釈処理を有効にしておく

f:id:m_shige1979:20161224015143p:plain

ファクトリーパスにdoma2のjarを設定する

f:id:m_shige1979:20161224015313p:plain

※これらの設定を行わないとdoma2のアノテーションでDaoの実装クラスを生成できない

プロジェクト構成

.
├── bin
│   ├── META-INF
│   │   └── com
│   │       └── example
│   │           └── dao
│   │               └── ItemDao
│   │                   └── findAll.sql
│   ├── com
│   │   └── example
│   │       ├── Sample01$1.class
│   │       ├── Sample01.class
│   │       ├── config
│   │       │   └── AppConfig.class
│   │       ├── dao
│   │       │   ├── ItemDao.class
│   │       │   └── ItemDaoImpl.class
│   │       └── entity
│   │           ├── Item.class
│   │           └── _Item.class
│   ├── doma-2.0.1.jar
│   └── mysql-connector-java-5.1.40.jar
├── lib
│   ├── doma-2.0.1.jar
│   └── mysql-connector-java-5.1.40.jar
└── src
    ├── META-INF
    │   └── com
    │       └── example
    │           └── dao
    │               └── ItemDao
    │                   └── findAll.sql
    └── com
        └── example
            ├── Sample01.java
            ├── config
            │   └── AppConfig.java
            ├── dao
            │   └── ItemDao.java
            └── entity
                └── Item.java

設定情報クラス

AppConfig
package com.example.config;

import javax.sql.DataSource;

import org.seasar.doma.SingletonConfig;
import org.seasar.doma.jdbc.Config;
import org.seasar.doma.jdbc.dialect.Dialect;
import org.seasar.doma.jdbc.dialect.MysqlDialect;
import org.seasar.doma.jdbc.tx.LocalTransactionDataSource;
import org.seasar.doma.jdbc.tx.LocalTransactionManager;
import org.seasar.doma.jdbc.tx.TransactionManager;

@SingletonConfig
public class AppConfig implements Config {
	
	private static final AppConfig INSTANCE = new AppConfig();

    private final Dialect dialect;
    private final LocalTransactionDataSource dataSource;
    private final TransactionManager transactionManager;

    private AppConfig() {
        dialect = new MysqlDialect();
        dataSource = new LocalTransactionDataSource("jdbc:mysql://192.168.33.10:3306/myapp?useSSL=false", "app", "Password123@");
        transactionManager = new LocalTransactionManager(dataSource.getLocalTransaction(getJdbcLogger()));
    }
	
	@Override
	public DataSource getDataSource() {
		// TODO Auto-generated method stub
		return dataSource;
	}

	@Override
	public Dialect getDialect() {
		// TODO Auto-generated method stub
		return dialect;
	}
	
	@Override
    public TransactionManager getTransactionManager() {
        return transactionManager;
    }

    public static AppConfig singleton() {
        return INSTANCE;
    }

}

エンティティ

Item
package com.example.entity;

import java.time.LocalDateTime;

import org.seasar.doma.Column;
import org.seasar.doma.Entity;
import org.seasar.doma.Id;
import org.seasar.doma.Table;

@Entity
@Table(name = "item")
public class Item {
	
	@Id
	private Integer id;
	
	@Column(name = "name")
	private String name;
	
	@Column(name = "price")
	private Integer price;
	
	@Column(name = "create_at")
	private LocalDateTime createAt;
	
	@Column(name = "update_at")
	private LocalDateTime updateAt;

	
	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getPrice() {
		return price;
	}

	public void setPrice(Integer price) {
		this.price = price;
	}

	public LocalDateTime getCreateAt() {
		return createAt;
	}

	public void setCreateAt(LocalDateTime createAt) {
		this.createAt = createAt;
	}

	public LocalDateTime getUpdateAt() {
		return updateAt;
	}

	public void setUpdateAt(LocalDateTime updateAt) {
		this.updateAt = updateAt;
	}
	
	
	
}

Dao

ItemDao
package com.example.dao;

import java.util.List;

import org.seasar.doma.Dao;
import org.seasar.doma.Select;

import com.example.config.AppConfig;
import com.example.entity.Item;

@Dao(config = AppConfig.class)
public interface ItemDao {
	
	@Select
    List<Item> findAll();
	
}
findAll.sql
select
    /*%expand*/*
from
    item
order by 
	id asc

実行用クラス

Sample01.java
package com.example;

import java.util.List;

import org.seasar.doma.jdbc.tx.TransactionManager;

import com.example.config.AppConfig;
import com.example.dao.ItemDao;
import com.example.dao.ItemDaoImpl;
import com.example.entity.Item;

public class Sample01 {

	public static void main(String[] args) {
		
		TransactionManager tm = AppConfig.singleton().getTransactionManager();
		tm.required(new Runnable(){

			@Override
			public void run() {
				// TODO Auto-generated method stub
				ItemDao dao = new ItemDaoImpl();
				List<Item> list = dao.findAll();
				
				System.out.println(list.size());
			}
			
		});
		
		

	}

}

実行結果

12 24, 2016 1:40:25 午前 org.seasar.doma.jdbc.tx.LocalTransaction begin
情報: [DOMA2063] ローカルトランザクション[1190654826]を開始しました。
12 24, 2016 1:40:25 午前 com.example.dao.ItemDaoImpl findAll
情報: [DOMA2220] ENTER  : クラス=[com.example.dao.ItemDaoImpl], メソッド=[findAll]
12 24, 2016 1:40:25 午前 com.example.dao.ItemDaoImpl findAll
情報: [DOMA2076] SQLログ : SQLファイル=[META-INF/com/example/dao/ItemDao/findAll.sql],
select
    id, name, price, create_at, update_at
from
    item
order by 
	id asc
12 24, 2016 1:40:25 午前 com.example.dao.ItemDaoImpl findAll
情報: [DOMA2221] EXIT   : クラス=[com.example.dao.ItemDaoImpl], メソッド=[findAll]
6
12 24, 2016 1:40:25 午前 org.seasar.doma.jdbc.tx.LocalTransaction commit
情報: [DOMA2067] ローカルトランザクション[1190654826]をコミットしました。
12 24, 2016 1:40:25 午前 org.seasar.doma.jdbc.tx.LocalTransaction commit
情報: [DOMA2064] ローカルトランザクション[1190654826]を終了しました。

動きました(^^)
ログにsqlを表示する?

所感

たいていはフレームワーク内で使用されているような記載がほとんどで単体で動かすサンプルが少なかったので助かりました。
sqlを記載できるのでSQLが好きな人には使えるような感じ…
動的な条件をどのような感じで設定できるか試していないのでちょっとやってみる。

Doma2のgenで自動生成を行う

Doma2

ORマッパーらしい
SQLファイルとか書いてなんかできるらしいよ

今回やること

Doma2でDBを参照していい感じのエンティティとかを作成できるらしいので準備して見る。

環境

eclipse
mysql

ライブラリ

doma2
doma2-gen
freemarker
mysl

設定

プロジェクトを作成する

f:id:m_shige1979:20161221001145p:plain

ライブラリを指定の場所へ配置しておく

f:id:m_shige1979:20161221001243p:plain

設定ファイル編集

doma-gen-build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="doma-gen-example" default="gen" basedir=".">
	<!-- javaDestDir -->
	<property name="javaDestDir" value="src"/>
	
	<!-- sqlDestDir -->
	<property name="sqlDestDir" value="src"/>
	
	<!-- dialectName -->
	<property name="dialectName" value="mysql"/>
	
	<!-- driverClassName -->
	<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
	<property name="url" value="jdbc:mysql://192.168.33.10:3306/myapp"/>
	<property name="user" value="app"/>
	<property name="password" value="Password123@"/>
	
	<!-- package -->
	<property name="entityPackageName" value="com.example.mshige1979.server.db.entity"/>
	<property name="daoPackageName" value="com.example.mshige1979.server.db.dao"/>
	
	
	<property name="configClassName" value="com.example.mshige1979.server.db.config.AppConfig"/>
	<property name="sqlTestClassName" value="com.example.mshige1979.server.db.SqlTest"/>
	<property name="sqlFilePattern" value="META-INF/**/*.sql"/>
	
	<path id="classpath">
		<fileset dir="WEB-INF/lib" includes="*.jar"/>
	</path>

	<taskdef name="gen" classname="org.seasar.doma.extension.gen.task.Gen" classpathref="classpath" loaderref="loader"/>
	<typedef name="entityConfig" classname="org.seasar.doma.extension.gen.task.EntityConfig" loaderref="loader"/>
	<typedef name="daoConfig" classname="org.seasar.doma.extension.gen.task.DaoConfig" loaderref="loader"/>
	<typedef name="sqlConfig" classname="org.seasar.doma.extension.gen.task.SqlConfig" loaderref="loader"/>
	
	<target name="gen">
		<gen
			dialectName="${dialectName}"
			driverClassName="${driverClassName}"
			url="${url}"
			user="${user}"
			password="${password}">
			<entityConfig
				destdir="${javaDestDir}"
				packageName="${entityPackageName}"
			/>
			<daoConfig
				destdir="${javaDestDir}"
				packageName="${daoPackageName}"
				configClassName="${configClassName}"
			/>
			<sqlConfig
				destdir="${sqlDestDir}"
			/>
		</gen>
	</target>

	<target name="genTest">
		<genTest
			dialectName="${dialectName}"
			driverClassName="${driverClassName}"
			url="${url}"
			user="${user}"
			password="${password}">
			<sqlTestConfig
				destdir="${javaDestDir}"
				testClassName="${sqlTestClassName}">
				<fileset dir="${sqlDestDir}">
					<include name="${sqlFilePattern}"/>
				</fileset>
			</sqlTestConfig>
		</genTest>
	</target>

</project>

実行する

f:id:m_shige1979:20161221001657p:plain

Buildfile: sample_spring_doma2a/doma-gen-build.xml
gen:
      [gen] [DOMAGEN0017] 方言にはクラス[org.seasar.doma.extension.gen.dialect.MysqlGenDialect]が使用されます。
      [gen] Wed Dec 21 00:17:40 JST 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
      [gen] [DOMAGEN0020] ファイルを上書きしました。/Users/matsumotoshigeharu/Documents/workspace/sample_spring_doma2a/src/com/example/mshige1979/server/db/entity/Item.java
      [gen] [DOMAGEN0020] ファイルを上書きしました。/Users/matsumotoshigeharu/Documents/workspace/sample_spring_doma2a/src/META-INF/com/example/mshige1979/server/db/dao/ItemDao/selectById.sql
      [gen] [DOMAGEN0020] ファイルを上書きしました。/Users/matsumotoshigeharu/Documents/workspace/sample_spring_doma2a/src/com/example/mshige1979/server/db/entity/Memo.java
      [gen] [DOMAGEN0020] ファイルを上書きしました。/Users/matsumotoshigeharu/Documents/workspace/sample_spring_doma2a/src/META-INF/com/example/mshige1979/server/db/dao/MemoDao/selectById.sql
      [gen] [DOMAGEN0020] ファイルを上書きしました。/Users/matsumotoshigeharu/Documents/workspace/sample_spring_doma2a/src/com/example/mshige1979/server/db/entity/ObjData.java
      [gen] [DOMAGEN0020] ファイルを上書きしました。/Users/matsumotoshigeharu/Documents/workspace/sample_spring_doma2a/src/META-INF/com/example/mshige1979/server/db/dao/ObjDataDao/selectById.sql
BUILD SUCCESSFUL
Total time: 1 second

できたもの(一部)

ItemDao.java
package com.example.mshige1979.server.db.dao;

import com.example.mshige1979.server.db.config.AppConfig;
import com.example.mshige1979.server.db.entity.Item;
import org.seasar.doma.Dao;
import org.seasar.doma.Delete;
import org.seasar.doma.Insert;
import org.seasar.doma.Select;
import org.seasar.doma.Update;

/**
 */
@Dao(config = AppConfig.class)
public interface ItemDao {

    /**
     * @param id
     * @return the Item entity
     */
    @Select
    Item selectById(Integer id);

    /**
     * @param entity
     * @return affected rows
     */
    @Insert
    int insert(Item entity);

    /**
     * @param entity
     * @return affected rows
     */
    @Update
    int update(Item entity);

    /**
     * @param entity
     * @return affected rows
     */
    @Delete
    int delete(Item entity);
}
Item.java
package com.example.mshige1979.server.db.entity;

import java.time.LocalDateTime;
import org.seasar.doma.Column;
import org.seasar.doma.Entity;
import org.seasar.doma.GeneratedValue;
import org.seasar.doma.GenerationType;
import org.seasar.doma.Id;
import org.seasar.doma.Table;

/**
 * 
 */
@Entity(listener = ItemListener.class)
@Table(name = "item")
public class Item {

    /**  */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    Integer id;

    /**  */
    @Column(name = "name")
    String name;

    /**  */
    @Column(name = "price")
    Integer price;

    /**  */
    @Column(name = "create_at")
    LocalDateTime createAt;

    /**  */
    @Column(name = "update_at")
    LocalDateTime updateAt;

    /** 
     * Returns the id.
     * 
     * @return the id
     */
    public Integer getId() {
        return id;
    }

    /** 
     * Sets the id.
     * 
     * @param id the id
     */
    public void setId(Integer id) {
        this.id = id;
    }

    /** 
     * Returns the name.
     * 
     * @return the name
     */
    public String getName() {
        return name;
    }

    /** 
     * Sets the name.
     * 
     * @param name the name
     */
    public void setName(String name) {
        this.name = name;
    }

    /** 
     * Returns the price.
     * 
     * @return the price
     */
    public Integer getPrice() {
        return price;
    }

    /** 
     * Sets the price.
     * 
     * @param price the price
     */
    public void setPrice(Integer price) {
        this.price = price;
    }

    /** 
     * Returns the createAt.
     * 
     * @return the createAt
     */
    public LocalDateTime getCreateAt() {
        return createAt;
    }

    /** 
     * Sets the createAt.
     * 
     * @param createAt the createAt
     */
    public void setCreateAt(LocalDateTime createAt) {
        this.createAt = createAt;
    }

    /** 
     * Returns the updateAt.
     * 
     * @return the updateAt
     */
    public LocalDateTime getUpdateAt() {
        return updateAt;
    }

    /** 
     * Sets the updateAt.
     * 
     * @param updateAt the updateAt
     */
    public void setUpdateAt(LocalDateTime updateAt) {
        this.updateAt = updateAt;
    }
}
selectById.sql
select
  /*%expand*/*
from
  item
where
  id = /* id */1

今回はここまで
おわりです