コードリファレンスの Github リンク
最新の Web アプリケーションでは、安全なユーザー認証が非常に重要です。従来、セッションベースの認証が広く使用されてきましたが、アプリケーションの分散化と拡張性が高まるにつれ、トークンベースの認証にはいくつかの利点があります。
トークンベースの認証により、アプリケーションをステートレスにすることができます。つまり、サーバーはセッション データを保存する必要がなく、スケーラブルな RESTful API に最適です。
このチュートリアルでは、Spring Security と JDBC テンプレートを使用して Spring Boot アプリケーションに JWT (JSON Web Token) 認証を実装する方法を説明します。
JWT (JSON Web Token) は、2 者間で転送されるクレームを表現するコンパクトで URL セーフな方法です。これは一般に、各リクエストが署名付きトークンを使用して認証されるステートレス認証に使用されます。
ステートレス認証
JWT トークンは自己完結型であり、ユーザーの認証情報をトークン ペイロード内で直接伝送するため、サーバーのメモリ使用量が削減され、スケーラビリティが向上します。
クロスプラットフォームのサポート
トークンはクライアントに安全に (ローカル ストレージや Cookie などに) 保存できるため、モバイル アプリケーションや Web アプリケーションで簡単に使用できます。
セキュリティ
各トークンはデジタル署名されているため、その整合性が保証され、サーバーはリクエストのたびにデータベースにクエリを実行することなくトークンを検証できます。
このチュートリアルでは、次の方法を学びます:
このチュートリアルが終わるまでに、Spring Boot と JWT を活用してアプリケーションにシームレスでスケーラブルなアクセス制御を提供する、安全でステートレスな認証システムが完成します。
API フロー:
テクノロジー要件:
Spring Web ツールまたは開発ツール (STS、Intellij、または任意の IDE) を使用して、Spring Boot プロジェクトを作成します。
pom.xml を開いて、Spring Security、JWT、および JDBC テンプレートの依存関係を追加します。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency>
src/main/resources フォルダーの下で、application.properties を開き、以下の構成を追加します。このチュートリアルでは postgres データベースを使用します。
spring.application.name= authmanager server.port= 1001 servlet.context-path= /authmanager database.username= postgres database.password= admin@123 database.driverClassName= org.postgresql.Driver database.jdbcUrl= jdbc:postgresql://localhost:5432/postgres database.maxActive= 5 database.minIdle= 5 database.poolName= Authmanager Postgres Datasource app.jwtSecret= ###############ib-Spring############### app.jwtExpirationMs= 3600000 app.jwtRefreshExpirationMs= 86400000
ユーザー情報、ロール、ユーザーとロールのマッピング、およびリフレッシュ トークンの単純なテーブル構造を定義します。
CREATE SCHEMA IB; ------------------------------------------------------------------------- create sequence users_uniqueid_seq START 1; create table ib.users( uniqueid bigint not null default nextval('users_uniqueid_seq') PRIMARY KEY, email varchar(75), password varchar(200), username varchar(20) ); insert into ib.users(email,password,username) values ('admin@ib.com','a$VcdzH8Q.o4KEo6df.XesdOmXdXQwT5ugNQvu1Pl0390rmfOeA1bhS','admin'); #(password = 12345678) ------------------------------------------------------------------------- create sequence roles_id_seq START 1; create table ib.roles( id int not null default nextval('roles_id_seq') PRIMARY KEY, name varchar(20) ); INSERT INTO ib.roles(name) VALUES('ROLE_USER'); INSERT INTO ib.roles(name) VALUES('ROLE_MODERATOR'); INSERT INTO ib.roles(name) VALUES('ROLE_ADMIN'); ------------------------------------------------------------------------- create table ib.user_roles( user_uniqueid bigint not null, role_id int not null, primary key(user_uniqueid,role_id) ); insert into ib.user_roles (user_uniqueid,role_id) values (1,3); ------------------------------------------------------------------------- create sequence refresh_tokens_id_seq START 1; create table ib.refresh_tokens( id bigint not null default nextval('refresh_tokens_id_seq') PRIMARY KEY, uniqueid bigint, token varchar(500) not null, expiryDate TIMESTAMP WITH TIME ZONE not null ); -------------------------------------------------------------------------
次のモデルを定義しましょう。
モデル パッケージで、以下の 4 つのファイルを作成します:
モデル/ERole.java
package com.security.authmanager.model; public enum ERole { ROLE_USER, ROLE_MODERATOR, ROLE_ADMIN }
モデル/Role.java
package com.security.authmanager.model; public class Role { private Integer id; private ERole name; public Role() { } public Role(ERole name) { this.name = name; } //generate getters and setters }
モデル/User.java
package com.security.authmanager.model; import java.util.HashSet; import java.util.Set; public class User { private Long id; private String username; private String email; private String password; private Set<Role> roles = new HashSet<>(); public User() { } public User(String username, String email, String password) { this.username = username; this.email = email; this.password = password; } //generate getters and setters }
モデル/RefreshToken.java
package com.security.authmanager.model; import java.util.HashSet; import java.util.Set; public class User { private Long id; private String username; private String email; private String password; private Set<Role> roles = new HashSet<>(); public User() { } public User(String username, String email, String password) { this.username = username; this.email = email; this.password = password; } //generate getters and setters }
CustomUserDetailsRepository クラスは、User エンティティとロール エンティティに関連するカスタム データベース操作を処理する Spring @Repository です。 JdbcTemplate を使用して、ユーザーの取得、ユーザー名または電子メールによるユーザーの存在の確認、新しいユーザーの作成、ロールの取得などのタスクの SQL クエリを実行します。
package com.security.authmanager.repository; import com.security.authmanager.common.QueryConstants; import com.security.authmanager.model.ERole; import com.security.authmanager.model.Role; import com.security.authmanager.model.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; import java.util.HashSet; import java.util.Objects; import java.util.Set; @Repository public class CustomUserDetailsRepository { private static final Logger logger = LoggerFactory.getLogger(CustomUserDetailsRepository.class); @Autowired private JdbcTemplate jdbcTemplate; public User fetchUserByUserName(String userName){ try{ return jdbcTemplate.query((conn) ->{ final PreparedStatement ps = conn.prepareStatement(QueryConstants.FETCH_USER); ps.setString(1, userName.toUpperCase()); return ps; },rs->{ User user = null; Set<Role> roles = new HashSet<>(); while (rs.next()) { if (user == null) { user = new User(); user.setEmail(rs.getString("email")); user.setId(rs.getLong("uniqueid")); user.setPassword(rs.getString("password")); user.setUsername(rs.getString("username")); } Role role = new Role(); role.setId(rs.getInt("id")); role.setName(ERole.valueOf(rs.getString("name"))); roles.add(role); } if (user != null) { user.setRoles(roles); } return user; }); }catch(Exception e){ logger.error("Exception in fetchUserByUserName()",e); throw new RuntimeException(e); } } public boolean existsByUsername(String userName) { try{ return jdbcTemplate.query((conn) -> { final PreparedStatement ps = conn.prepareStatement(QueryConstants.CHECK_USER_BY_USERNAME); ps.setString(1, userName.toUpperCase()); return ps; }, (rs,rownum) -> rs.getInt("count")).get(0)>0; }catch(Exception e){ logger.error("Exception in existsByUsername()",e); throw new RuntimeException(e); } } public boolean existsByEmail(String email) { try{ return jdbcTemplate.query((conn) -> { final PreparedStatement ps = conn.prepareStatement(QueryConstants.CHECK_USER_BY_EMAIL); ps.setString(1, email.toUpperCase()); return ps; }, (rs,rownum) -> rs.getInt("count")).get(0)>0; }catch(Exception e){ logger.error("Exception in existsByEmail()",e); throw new RuntimeException(e); } } public Role findRoleByName(ERole eRole) { try{ return jdbcTemplate.query((conn) -> { final PreparedStatement ps = conn.prepareStatement(QueryConstants.FETCH_ROLE_BY_NAME); ps.setString(1, String.valueOf(eRole)); return ps; }, rs -> { Role role=null; while(rs.next()){ role = new Role(); role.setName(ERole.valueOf(rs.getString("name"))); role.setId(rs.getInt("id")); } return role; }); }catch(Exception e){ logger.error("Exception in findRoleByName()",e); throw new RuntimeException(e); } } public void createUser(User user) { try(Connection conn = Objects.requireNonNull(jdbcTemplate.getDataSource()).getConnection()){ try (PreparedStatement userStatement = conn.prepareStatement(QueryConstants.INSERT_TO_USERS,Statement.RETURN_GENERATED_KEYS)) { userStatement.setString(1, user.getEmail().toUpperCase()); userStatement.setString(2, user.getPassword()); userStatement.setString(3, user.getUsername().toUpperCase()); userStatement.executeUpdate(); // Retrieve generated userId try (ResultSet generatedKeys = userStatement.getGeneratedKeys()) { if (generatedKeys.next()) { Long userId = generatedKeys.getLong(1); // Assuming userId is of type VARCHAR logger.info("gen userid {}",userId.toString()); user.setId(userId); } } } if (user.getRoles() != null && !user.getRoles().isEmpty()) { try (PreparedStatement userRoleStatement = conn.prepareStatement(QueryConstants.INSERT_TO_USER_ROLES)) { for (Role role : user.getRoles()) { userRoleStatement.setLong(1, user.getId()); userRoleStatement.setLong(2, role.getId()); userRoleStatement.executeUpdate(); } } } }catch(Exception e){ logger.error("Exception in existsByEmail()",e); throw new RuntimeException(e); } } }
このリポジトリは、データベース内のユーザーとロールのデータを管理するためのカスタム SQL ベースの CRUD 操作を実行します。
主な機能:
RefreshTokenRepository クラスは、RefreshToken エンティティに関連するデータベース操作を処理する Spring @Repository です。 Spring の JdbcTemplate を使用して、生の SQL クエリを通じてデータベースと対話し、リフレッシュ トークンの保存、削除、取得のロジックをカプセル化します。
package com.security.authmanager.repository; import com.security.authmanager.common.QueryConstants; import com.security.authmanager.model.ERole; import com.security.authmanager.model.RefreshToken; import com.security.authmanager.model.Role; import com.security.authmanager.model.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.sql.PreparedStatement; import java.sql.Timestamp; import java.util.Optional; @Repository public class RefreshTokenRepository { private static final Logger logger = LoggerFactory.getLogger(RefreshTokenRepository.class); @Autowired private JdbcTemplate jdbcTemplate; public void deleteRefreshToken(RefreshToken refreshToken) { try{ jdbcTemplate.update(QueryConstants.DELETE_REFRESH_TOKEN,(final PreparedStatement ps) ->{ ps.setString(1,refreshToken.getToken()); }); }catch (Exception e){ logger.error("Exception in deleteRefreshToken()",e); throw new RuntimeException(e); } } public int deleteRefreshTokenByUser(User user) { return 0; } public RefreshToken saveRefreshToken(RefreshToken refreshToken) { try{ jdbcTemplate.update(QueryConstants.SAVE_REFRESH_TOKEN,(final PreparedStatement ps) ->{ ps.setLong(1,refreshToken.getUser().getId()); ps.setString(2,refreshToken.getToken()); ps.setTimestamp(3, Timestamp.from(refreshToken.getExpiryDate())); }); }catch (Exception e){ logger.error("Exception in saveRefreshToken()",e); throw new RuntimeException(e); } return refreshToken; } public Optional<RefreshToken> findByToken(String token) { RefreshToken refreshToken = new RefreshToken(); try{ return Optional.ofNullable(jdbcTemplate.query((conn) -> { final PreparedStatement ps = conn.prepareStatement(QueryConstants.FIND_BY_TOKEN); ps.setString(1, token); return ps; }, rs -> { User user = new User(); while (rs.next()) { refreshToken.setId(rs.getLong("id")); refreshToken.setToken(rs.getString("token")); refreshToken.setExpiryDate(rs.getTimestamp("expiryDate").toInstant()); user.setId(rs.getLong("uniqueid")); user.setEmail(rs.getString("email")); user.setUsername(rs.getString("username")); refreshToken.setUser(user); } return refreshToken; })); }catch(Exception e){ logger.error("Exception in findByToken()",e); throw new RuntimeException(e); } } }
このリポジトリは、RefreshToken エンティティに対する CRUD 操作のためにデータベースと直接対話します。
主な機能:
WebSecurityConfig は、JWT ベースのトークン認証を使用するように Spring Security を構成します。
AuthTokenFilter (JWT トークンの処理用)、DaoAuthenticationProvider (ユーザー詳細の取得とパスワードの検証用)、BCryptPasswordEncoder (パスワードのハッシュ化と比較用) など、認証に必要なさまざまなコンポーネントの Bean を定義します。
SecurityFilterChain は、受信 HTTP リクエストを保護する方法を構成します。
- 特定のパブリック ルート (/auth/**、/test/**) へのアクセスを許可します。
- 他のすべてのルートを保護し、認証を必要とします。
- セッション管理を無効にします (システムをステートレスにします)。
-
の JWT トークンをインターセプトして処理するフィルターを構成します。
ユーザー認証。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency>
このクラスは、現在認証されているユーザーを表すために Spring Security で主に使用されます。
ユーザーがログインしようとしたとき:
UserDetailsImpl は、User エンティティと Spring Security の認証および認可のための内部メカニズムの間のブリッジです。
spring.application.name= authmanager server.port= 1001 servlet.context-path= /authmanager database.username= postgres database.password= admin@123 database.driverClassName= org.postgresql.Driver database.jdbcUrl= jdbc:postgresql://localhost:5432/postgres database.maxActive= 5 database.minIdle= 5 database.poolName= Authmanager Postgres Datasource app.jwtSecret= ###############ib-Spring############### app.jwtExpirationMs= 3600000 app.jwtRefreshExpirationMs= 86400000
以下はクエリです: (QueryConstants.java)
CREATE SCHEMA IB; ------------------------------------------------------------------------- create sequence users_uniqueid_seq START 1; create table ib.users( uniqueid bigint not null default nextval('users_uniqueid_seq') PRIMARY KEY, email varchar(75), password varchar(200), username varchar(20) ); insert into ib.users(email,password,username) values ('admin@ib.com','a$VcdzH8Q.o4KEo6df.XesdOmXdXQwT5ugNQvu1Pl0390rmfOeA1bhS','admin'); #(password = 12345678) ------------------------------------------------------------------------- create sequence roles_id_seq START 1; create table ib.roles( id int not null default nextval('roles_id_seq') PRIMARY KEY, name varchar(20) ); INSERT INTO ib.roles(name) VALUES('ROLE_USER'); INSERT INTO ib.roles(name) VALUES('ROLE_MODERATOR'); INSERT INTO ib.roles(name) VALUES('ROLE_ADMIN'); ------------------------------------------------------------------------- create table ib.user_roles( user_uniqueid bigint not null, role_id int not null, primary key(user_uniqueid,role_id) ); insert into ib.user_roles (user_uniqueid,role_id) values (1,3); ------------------------------------------------------------------------- create sequence refresh_tokens_id_seq START 1; create table ib.refresh_tokens( id bigint not null default nextval('refresh_tokens_id_seq') PRIMARY KEY, uniqueid bigint, token varchar(500) not null, expiryDate TIMESTAMP WITH TIME ZONE not null ); -------------------------------------------------------------------------
UserDetailsServiceImpl クラスは、アプリケーションのデータベースと Spring Security の認証プロセスの間のブリッジとして機能します。 CustomUserDetailsRepository を使用してデータベースからユーザーの詳細を取得し、User オブジェクトを UserDetailsImpl (Spring Security に適した形式) に変換し、ユーザーが見つからない場合は例外をスローして処理します。このサービスにより、Spring Security はユーザーを認証し、ユーザーのロールと権限に基づいて認可を管理できるようになります。
package com.security.authmanager.model; public enum ERole { ROLE_USER, ROLE_MODERATOR, ROLE_ADMIN }
AuthTokenFilter クラスは Spring の OncePerRequestFilter を拡張し、リクエスト チェーン内のすべての HTTP リクエストを 1 回処理するフィルタにします。その主な役割は、リクエストから JWT (JSON Web Token) を抽出して検証し、トークンが有効であれば Spring Security の SecurityContext にユーザーの認証を設定することです。
package com.security.authmanager.model; public class Role { private Integer id; private ERole name; public Role() { } public Role(ERole name) { this.name = name; } //generate getters and setters }
リクエストが行われるたびに:
このフィルタにより、有効な JWT トークンを含むすべてのリクエストが自動的に認証され、ユーザーがログイン後の後続のリクエストで資格情報 (ユーザー名/パスワードなど) を入力する必要がなくなります。
RefreshTokenService クラスは、トークンベースの認証システムでのリフレッシュ トークンの管理に関連するサービスを提供します。リフレッシュ トークンは、最初の JWT の有効期限が切れた後、ユーザーの再認証を必要とせずに新しい JWT トークンを取得するために使用されます。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency>
RefreshTokenService は、リフレッシュ トークンの作成、検証、削除を処理します。トークンとユーザーをデータベースに保存したり、データベースから取得したりするためにリポジトリを使用します。
このサービスは、認証システムの重要な部分であり、JWT の有効期限が切れた後に再度資格情報を提供する必要なく、リフレッシュ トークンを使用してユーザーがログインした状態を維持します。
JwtUtils クラスは、Spring Boot アプリケーションで認証を目的とした JWT (JSON Web Token) の作成、解析、検証を処理するユーティリティ クラスです。 JWT を操作するために jjwt ライブラリを使用します。
spring.application.name= authmanager server.port= 1001 servlet.context-path= /authmanager database.username= postgres database.password= admin@123 database.driverClassName= org.postgresql.Driver database.jdbcUrl= jdbc:postgresql://localhost:5432/postgres database.maxActive= 5 database.minIdle= 5 database.poolName= Authmanager Postgres Datasource app.jwtSecret= ###############ib-Spring############### app.jwtExpirationMs= 3600000 app.jwtRefreshExpirationMs= 86400000
JwtUtils クラスは、JWT トークンの生成、解析、検証を担当します。秘密鍵 (HMAC-SHA256) を使用してトークンに安全に署名し、正しい秘密鍵を所有する当事者のみがトークンを読み取りまたは検証できるようにします。
このクラスは、ユーザーにアクセスを許可する前に、トークンからユーザー名を抽出し、トークンが有効かどうかを確認します。このユーティリティは、アプリケーションで安全なトークンベースの認証を維持するために不可欠です。
AuthEntryPointJwt クラスは、Spring Security の AuthenticationEntryPoint インターフェイスを実装します。これは、不正なリクエストが行われたとき、通常はユーザーが有効な認証なしで保護されたリソースにアクセスしようとしたとき (JWT がない、または無効な JWT など) に何が起こるかを処理します。
CREATE SCHEMA IB; ------------------------------------------------------------------------- create sequence users_uniqueid_seq START 1; create table ib.users( uniqueid bigint not null default nextval('users_uniqueid_seq') PRIMARY KEY, email varchar(75), password varchar(200), username varchar(20) ); insert into ib.users(email,password,username) values ('admin@ib.com','a$VcdzH8Q.o4KEo6df.XesdOmXdXQwT5ugNQvu1Pl0390rmfOeA1bhS','admin'); #(password = 12345678) ------------------------------------------------------------------------- create sequence roles_id_seq START 1; create table ib.roles( id int not null default nextval('roles_id_seq') PRIMARY KEY, name varchar(20) ); INSERT INTO ib.roles(name) VALUES('ROLE_USER'); INSERT INTO ib.roles(name) VALUES('ROLE_MODERATOR'); INSERT INTO ib.roles(name) VALUES('ROLE_ADMIN'); ------------------------------------------------------------------------- create table ib.user_roles( user_uniqueid bigint not null, role_id int not null, primary key(user_uniqueid,role_id) ); insert into ib.user_roles (user_uniqueid,role_id) values (1,3); ------------------------------------------------------------------------- create sequence refresh_tokens_id_seq START 1; create table ib.refresh_tokens( id bigint not null default nextval('refresh_tokens_id_seq') PRIMARY KEY, uniqueid bigint, token varchar(500) not null, expiryDate TIMESTAMP WITH TIME ZONE not null ); -------------------------------------------------------------------------
AuthEntryPointJwt クラスは、不正アクセスの試みを遮断し、401 エラー コード、エラー メッセージ、リクエストの詳細を含む構造化された JSON 応答を返すカスタム エントリ ポイントです。
エラーをログに記録し、認証が失敗した場合にクライアントに明確でユーザーフレンドリーな応答を提供します。
以下は RestAPI のペイロードです:
1.リクエスト:
- LoginRequest.java :
package com.security.authmanager.model; public enum ERole { ROLE_USER, ROLE_MODERATOR, ROLE_ADMIN }
- SignupRequest.java :
package com.security.authmanager.model; public class Role { private Integer id; private ERole name; public Role() { } public Role(ERole name) { this.name = name; } //generate getters and setters }
- TokenRefreshRequest.java :
package com.security.authmanager.model; import java.util.HashSet; import java.util.Set; public class User { private Long id; private String username; private String email; private String password; private Set<Role> roles = new HashSet<>(); public User() { } public User(String username, String email, String password) { this.username = username; this.email = email; this.password = password; } //generate getters and setters }
2.応答:
- JwtResponse.java
package com.security.authmanager.model; import java.util.HashSet; import java.util.Set; public class User { private Long id; private String username; private String email; private String password; private Set<Role> roles = new HashSet<>(); public User() { } public User(String username, String email, String password) { this.username = username; this.email = email; this.password = password; } //generate getters and setters }
- MessageResponse.java
package com.security.authmanager.repository; import com.security.authmanager.common.QueryConstants; import com.security.authmanager.model.ERole; import com.security.authmanager.model.Role; import com.security.authmanager.model.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; import java.util.HashSet; import java.util.Objects; import java.util.Set; @Repository public class CustomUserDetailsRepository { private static final Logger logger = LoggerFactory.getLogger(CustomUserDetailsRepository.class); @Autowired private JdbcTemplate jdbcTemplate; public User fetchUserByUserName(String userName){ try{ return jdbcTemplate.query((conn) ->{ final PreparedStatement ps = conn.prepareStatement(QueryConstants.FETCH_USER); ps.setString(1, userName.toUpperCase()); return ps; },rs->{ User user = null; Set<Role> roles = new HashSet<>(); while (rs.next()) { if (user == null) { user = new User(); user.setEmail(rs.getString("email")); user.setId(rs.getLong("uniqueid")); user.setPassword(rs.getString("password")); user.setUsername(rs.getString("username")); } Role role = new Role(); role.setId(rs.getInt("id")); role.setName(ERole.valueOf(rs.getString("name"))); roles.add(role); } if (user != null) { user.setRoles(roles); } return user; }); }catch(Exception e){ logger.error("Exception in fetchUserByUserName()",e); throw new RuntimeException(e); } } public boolean existsByUsername(String userName) { try{ return jdbcTemplate.query((conn) -> { final PreparedStatement ps = conn.prepareStatement(QueryConstants.CHECK_USER_BY_USERNAME); ps.setString(1, userName.toUpperCase()); return ps; }, (rs,rownum) -> rs.getInt("count")).get(0)>0; }catch(Exception e){ logger.error("Exception in existsByUsername()",e); throw new RuntimeException(e); } } public boolean existsByEmail(String email) { try{ return jdbcTemplate.query((conn) -> { final PreparedStatement ps = conn.prepareStatement(QueryConstants.CHECK_USER_BY_EMAIL); ps.setString(1, email.toUpperCase()); return ps; }, (rs,rownum) -> rs.getInt("count")).get(0)>0; }catch(Exception e){ logger.error("Exception in existsByEmail()",e); throw new RuntimeException(e); } } public Role findRoleByName(ERole eRole) { try{ return jdbcTemplate.query((conn) -> { final PreparedStatement ps = conn.prepareStatement(QueryConstants.FETCH_ROLE_BY_NAME); ps.setString(1, String.valueOf(eRole)); return ps; }, rs -> { Role role=null; while(rs.next()){ role = new Role(); role.setName(ERole.valueOf(rs.getString("name"))); role.setId(rs.getInt("id")); } return role; }); }catch(Exception e){ logger.error("Exception in findRoleByName()",e); throw new RuntimeException(e); } } public void createUser(User user) { try(Connection conn = Objects.requireNonNull(jdbcTemplate.getDataSource()).getConnection()){ try (PreparedStatement userStatement = conn.prepareStatement(QueryConstants.INSERT_TO_USERS,Statement.RETURN_GENERATED_KEYS)) { userStatement.setString(1, user.getEmail().toUpperCase()); userStatement.setString(2, user.getPassword()); userStatement.setString(3, user.getUsername().toUpperCase()); userStatement.executeUpdate(); // Retrieve generated userId try (ResultSet generatedKeys = userStatement.getGeneratedKeys()) { if (generatedKeys.next()) { Long userId = generatedKeys.getLong(1); // Assuming userId is of type VARCHAR logger.info("gen userid {}",userId.toString()); user.setId(userId); } } } if (user.getRoles() != null && !user.getRoles().isEmpty()) { try (PreparedStatement userRoleStatement = conn.prepareStatement(QueryConstants.INSERT_TO_USER_ROLES)) { for (Role role : user.getRoles()) { userRoleStatement.setLong(1, user.getId()); userRoleStatement.setLong(2, role.getId()); userRoleStatement.executeUpdate(); } } } }catch(Exception e){ logger.error("Exception in existsByEmail()",e); throw new RuntimeException(e); } } }
- TokenRefreshResponse.java
package com.security.authmanager.repository; import com.security.authmanager.common.QueryConstants; import com.security.authmanager.model.ERole; import com.security.authmanager.model.RefreshToken; import com.security.authmanager.model.Role; import com.security.authmanager.model.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.sql.PreparedStatement; import java.sql.Timestamp; import java.util.Optional; @Repository public class RefreshTokenRepository { private static final Logger logger = LoggerFactory.getLogger(RefreshTokenRepository.class); @Autowired private JdbcTemplate jdbcTemplate; public void deleteRefreshToken(RefreshToken refreshToken) { try{ jdbcTemplate.update(QueryConstants.DELETE_REFRESH_TOKEN,(final PreparedStatement ps) ->{ ps.setString(1,refreshToken.getToken()); }); }catch (Exception e){ logger.error("Exception in deleteRefreshToken()",e); throw new RuntimeException(e); } } public int deleteRefreshTokenByUser(User user) { return 0; } public RefreshToken saveRefreshToken(RefreshToken refreshToken) { try{ jdbcTemplate.update(QueryConstants.SAVE_REFRESH_TOKEN,(final PreparedStatement ps) ->{ ps.setLong(1,refreshToken.getUser().getId()); ps.setString(2,refreshToken.getToken()); ps.setTimestamp(3, Timestamp.from(refreshToken.getExpiryDate())); }); }catch (Exception e){ logger.error("Exception in saveRefreshToken()",e); throw new RuntimeException(e); } return refreshToken; } public Optional<RefreshToken> findByToken(String token) { RefreshToken refreshToken = new RefreshToken(); try{ return Optional.ofNullable(jdbcTemplate.query((conn) -> { final PreparedStatement ps = conn.prepareStatement(QueryConstants.FIND_BY_TOKEN); ps.setString(1, token); return ps; }, rs -> { User user = new User(); while (rs.next()) { refreshToken.setId(rs.getLong("id")); refreshToken.setToken(rs.getString("token")); refreshToken.setExpiryDate(rs.getTimestamp("expiryDate").toInstant()); user.setId(rs.getLong("uniqueid")); user.setEmail(rs.getString("email")); user.setUsername(rs.getString("username")); refreshToken.setUser(user); } return refreshToken; })); }catch(Exception e){ logger.error("Exception in findByToken()",e); throw new RuntimeException(e); } } }
- AuthController.java
AuthController クラスは、アプリケーション内の認証関連のエンドポイントの処理を担当する Spring @RestController です。ユーザーのログイン、登録、トークン更新操作のためのエンドポイントを提供します。
主な機能:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency>
- TestController.java
TestController クラスは、ユーザーの役割に基づいてアクセス制御をテストするためのいくつかのエンドポイントを提供する Spring @RestController です。 Spring Security のロールベースの承認を使用して、アプリケーションの特定の部分へのアクセスを制限する方法を示します。
主な機能:
spring.application.name= authmanager server.port= 1001 servlet.context-path= /authmanager database.username= postgres database.password= admin@123 database.driverClassName= org.postgresql.Driver database.jdbcUrl= jdbc:postgresql://localhost:5432/postgres database.maxActive= 5 database.minIdle= 5 database.poolName= Authmanager Postgres Datasource app.jwtSecret= ###############ib-Spring############### app.jwtExpirationMs= 3600000 app.jwtRefreshExpirationMs= 86400000
1. MODとユーザーとして登録します。 (サインイン)
2.ログインしてアクセストークンを取得します。
3.リフレッシュトークンAPIを取得します。
4.アクセス トークンを渡してユーザー アクセスをテストします。
5.アクセス トークンを渡して MOD アクセスをテストします。
6.同じアクセス トークンを渡して管理者アクセスをテストします。
このユーザーには管理者アクセス権がないため権限がありません (ユーザーには mod とユーザーの役割しかありません)
楽しく学習してください!また会いましょう?
以上がSpring Security、JWT、および JDBC テンプレートを使用した Spring Boot でのトークンベースの認証の実装の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。