20.コントローラを使用して登録ページを転送します
ユーザーが登録したregister.htmlファイルをテンプレートフォルダーに移動します。
SystemController
追加:
@GetMapping("/register.html")
public String register() {
return "register";
}
でSecurityConfig
、登録関連"/register.html"
および"/portal/user/student/register"
これらの2つのURLをホワイトリストに追加します。
21.ユーザー権限の処理
21.1。完了:学生が登録するときに役割を割り当てる
「学生登録」ビジネスでは、新しく挿入されたユーザーデータのIDを時間内に取得し、ユーザーIDとロールID(学生ロールのIDは2に固定)をuser_role
データテーブルに挿入して、新しく登録された学生を記録する必要があります。キャラクター。
最初にUserServiceImpl
追加:
@Autowired
private UserRoleMapper userRoleMapper;
次に、元の「学生登録」ビジネスで、最後に以下を追加します。
// 向“用户角色表”中插入数据,为当前学生账号分配角色
UserRole userRole = new UserRole();
userRole.setUserId(user.getId());
userRole.setRoleId(2); // 学生角色的id固定为2,具体可参见user_role数据表
rows = userRoleMapper.insert(userRole);
// 判断返回值(受影响的行数)是否不为1
if (rows != 1) {
// 是:受影响的行数不是1,则插入用户角色数据失败,抛出InsertException
throw new InsertException("注册失败!服务器忙,请稍后再次尝试!");
}
完了後、「学生登録」のビジネスメソッドの前@Transactional
に、トランザクションを有効にするためのメモを追加する必要があります。
トランザクションに関しては、データベースが提供するメカニズムであり、一連の書き込み操作(挿入、削除、変更を含む)が成功または失敗することを保証できます。
データがあると仮定します:
口座番号 | 残高 |
---|---|
カン・ソング | 1000 |
グオビン | 8000 |
「Guobinが5000元をCangsongに転送する」ことを実現したい場合、実行する必要があるデータ操作は次のとおりです。
UPDATE 账户表 SET 余额=余额-5000 WHERE 账号='国斌';
UPDATE 账户表 SET 余额=余额+5000 WHERE 账号='苍松';
場合によっては、実行プロセス中に、いくつかの制御不能な要因により、前のSQLステートメントは正常に実行されますが、後者のSQLステートメントは実行できず、データセキュリティの問題が発生します。この場合、トランザクションを使用する必要があります。2つのSQLステートメントが正常に実行された場合、それらは正常に完了します。SQLステートメントのいずれかが正しく実行されなかった場合、すべてが失敗したことを確認する限り(一部のSQLステートメントが以前に正常に実行された場合でも)、失敗します)そしてデータセキュリティは影響を受けません!
Spring JDBCトランザクション処理に基づいて、ビジネスメソッドの前に@Transactional
注釈を追加するだけで済みます。処理メカニズムはおおよそ次のとおりです。
try {
开启事务:BEGIN
执行若干个数据访问操作(增、删、改、查)
提交事务(保存数据):COMMIT
} catch (RuntimeException e) {
回滚事务:ROLLBACK
}
したがって、トランザクションメカニズムを効果的に実行するには、次のことを行う必要があります。
- ビジネスに2つ以上の書き込み操作が含まれる場合(たとえば、2つのINSERT操作、または1つのINSERTと1つのDELETEなど)、ビジネスメソッドの前に
@Transactional
注釈を追加してトランザクションを有効にする必要があります。 - 永続層の書き込み操作が呼び出されるたびに、返された「影響を受ける行の数」を時間内に取得する必要があり、戻り値が期待値と一致するかどうかを判断する必要があります。一致しない場合は、スローする
RuntimeException
か、その子孫クラスの例外オブジェクト!
プロジェクトを開発するときに、ビジネス例外を継承する必要があるの
RuntimeException
は、次の理由からです。
- 例外の使用を回避するためのコードの記述は簡単です。
RuntimeException
その子孫は例外try...catch
またはに必須ではなくthrow/throws
、サービスレイヤーが例外をスローした後、コントローラーレイヤーもすべて再びスローされるため、スローまたはキャッチを宣言するための厳密な構文が必要です。処理のための統一された例外処理メカニズムに渡されます。- トランザクションメカニズムの通常の使用を確認します。
さらに@Transactional
、ビジネスクラスの宣言の前に注釈を追加することもできます。これにより、現在のクラスのすべてのメソッドがトランザクションメカニズムに基づいて実行されます。ただし、これは通常必要ないため、お勧めしません。
トランザクションのACID特性、トランザクションの分離、およびトランザクションの伝播についても理解する必要があります。
21.2。ログインを処理する際の許可の取得
上記の登録プロセスでは、「ロールの割り当て」が追加され、各ロールは特定の権限に対応しているため、「ロールの割り当て」のプロセスは「権限の割り当て」のプロセスです。ユーザーがログインするとき、ユーザーのアクセス許可を読み取って検証プロセスでSpring Securityの承認を完了する必要があります。これにより、特定のユーザーが特定の操作を実行できるように、将来特定のアクセスが行われたときに正しい判断ができるようになります。他のユーザーは権限がないため、これらの操作を実行できない場合があります。
まず、「ユーザーIDに基づいてユーザーの権限を照会する」機能を実装する必要があります。実行する必要があるSQLステートメントは、おおよそ次のとおりです。
SELECT
DISTINCT permission.*
FROM
permission
LEFT JOIN role_permission ON permission.id=role_permission.permission_id
LEFT JOIN role ON role_permission.role_id=role.id
LEFT JOIN user_role ON role.id=user_role.role_id
LEFT JOIN user ON user_role.user_id=user.id
WHERE
user.id=1;
パーミッションデータPermissionMapper
を処理する永続メソッドに抽象メソッドを追加します。
/**
* 查询某用户的权限
* @param userId 用户的id
* @return 该用户的权限的列表
*/
List<Permission> selectByUserId(Integer userId);
次に、PermissionMapper.xml
上記の抽象メソッドに対応するSQLステートメントを次のように構成します。
<select id="selectByUserId" resultMap="BaseResultMap">
SELECT
DISTINCT permission.id, permission.name, permission.description
FROM
permission
LEFT JOIN role_permission ON permission.id=role_permission.permission_id
LEFT JOIN role ON role_permission.role_id=role.id
LEFT JOIN user_role ON role.id=user_role.role_id
LEFT JOIN user ON user_role.user_id=user.id
WHERE
user.id=#{userId}
</select>
完了したら、テスト場所にPermissionMapperTests
テストクラスを作成し、単体テストを記述して実行します。
package cn.tedu.straw.portal.mapper;
@SpringBootTest
@Slf4j
public class PermissionMapperTests {
@Autowired
PermissionMapper mapper;
@Test
void selectByUserId() {
Integer userId = 1;
List<Permission> permissions = mapper.selectByUserId(userId);
log.debug("permissions count={}", permissions.size());
for (Permission permission : permissions) {
log.debug("permission > {}", permission);
}
}
}
次に、ログインを処理するビジネス、つまりUserServiceImpl
、最初に追加します。
@Autowired
private PermissionMapper permissionMapper;
そしてlogin()
メソッドに追加します:
// 权限字符串数组
List<Permission> permissions = permissionMapper.selectByUserId(user.getId());
String[] authorities = new String[permissions.size()];
for (int i = 0; i < permissions.size(); i++) {
authorities[i] = permissions.get(i).getName();
}
// 组织“用户详情”对象
UserDetails userDetails = org.springframework.security.core.userdetails.User
.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(authorities)
.disabled(user.getEnabled() == 0)
.accountLocked(user.getLocked() == 1)
.build();
登録されたビジネスの変更により(「学生アカウントに役割を割り当てる」が追加されたばかりです)、元のテストデータを使用できない場合があります。その後のテストでの使用を容易にするために、最初にすべての元のデータをクリアする必要があります。
TRUNCATE user;
また、登録ビジネスまたは登録ページから、いくつかの新しいアカウントを再度登録してください。
同時に、いくつかのデータは教師として識別されるべきです:
UPDATE user SET type=1 WHERE id IN (1, 2, 3);
ユーザー役割の割り当てテーブルで、元のデータを消去し、一部のアカウントの役割を管理者と教師に変更します。
-- 清空用户角色分配表
TRUNCATE user_role;
-- 将某些用户分配为管理员、老师、学生
INSERT INTO user_role (user_id, role_id) VALUES (1, 1), (1, 2), (1, 3);
-- 将某些用户分配为老师
INSERT INTO user_role (user_id, role_id) VALUES (2, 3), (3, 3);
-- 将某些用户分配为学生
INSERT INTO user_role (user_id, role_id) VALUES (4, 2), (5, 2), (6, 2);
22. Spring Securityを通じて現在ログインしているユーザーの情報を取得する
ユーザーが正常にログインした後、ユーザーの権限を取得する、ユーザーの質問リストを取得する、ユーザーの個人情報を取得するなど、後続の操作を実行するには、ユーザーの情報を取得する必要があります。
Spring Securityは、現在ログインしているユーザーの情報を取得する簡単な方法を提供します。コントローラーのリクエストを処理するメソッドで、タイプパラメーターをAuthentication
追加するか、Principal
タイプパラメーターを追加して、現在ログインしているユーザーの情報を取得します。次に例を示します。
// http://localhost:8080/test/user/current/authentication
@GetMapping("/user/current/authentication")
public Authentication getAuthentication(Authentication authentication) {
return authentication;
}
// http://localhost:8080/test/user/current/principal
@GetMapping("/user/current/principal")
public Principal getPrincipal(Principal principal) {
return principal;
}
上記の2つのメソッドの出力結果はまったく同じです。これらAuthentication
はから継承されているためPrincipal
、Spring MVCフレームワークがパラメーター値を挿入しようとすると、同じオブジェクトが挿入されます。
上記のメソッドは多くのコンテンツを出力し、次のメソッドを使用してユーザー情報を取得することもできます。
// http://localhost:8080/test/user/current/details
@GetMapping("/user/current/details")
public UserDetails getUserDetails(@AuthenticationPrincipal UserDetails userDetails) {
return userDetails;
}
23. UserDetailsを拡張する
上記の注入後、@AuthenticationPricipal UserDetails userDetails
ユーザーの情報は取得できますが、オブジェクトにカプセル化された情報は、ユーザーのid
属性やその他の属性など、プログラミングのニーズを満たすのに十分ではない場合があります。これらの属性が存在する必要がある場合は、クラスをカスタマイズして拡張する必要がありますUserDetails
。
cn.tedu.straw.portal.security
パッケージの下にUserInfo
クラスを作成し、クラスから継承してUser
、このクラスで必要なカスタム属性を宣言します。
package cn.tedu.straw.portal.security;
@Setter
@Getter
@ToString
public class UserInfo extends User {
private Integer id;
private String nickname;
private Integer gender;
private Integer type;
public UserInfo(String username, String password,
Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public UserInfo(String username, String password,
boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
}
}
注:親クラスUser
にはパラメーターのない構築メソッドがないため、継承後にパラメーターが一致する構築メソッドを追加する必要があります。
注:親のUser
ノンパラメトリックメソッドは構成に存在しないため、Lombok @Data
アノテーションで使用できず、オンデマンド@Setter
で追加できます@Getter
。
次に、ビジネスレイヤーでユーザーログインを処理するときに、上記で作成したUserInfo
オブジェクトのタイプを戻り値オブジェクトとして使用します。
// 组织“用户详情”对象
UserDetails userDetails = org.springframework.security.core.userdetails.User
.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(authorities)
.disabled(user.getEnabled() == 0)
.accountLocked(user.getLocked() == 1)
.build();
UserInfo userInfo = new UserInfo(
userDetails.getUsername(),
userDetails.getPassword(),
userDetails.isEnabled(),
userDetails.isAccountNonExpired(),
userDetails.isCredentialsNonExpired(),
userDetails.isAccountNonLocked(),
userDetails.getAuthorities()
);
userInfo.setId(user.getId());
userInfo.setNickname(user.getNickname());
userInfo.setGender(user.getGender());
userInfo.setType(user.getType());
return userInfo;
今後、現在ログインしているユーザーの情報を取得する必要がある場合は、コントローラーのリクエストを処理するメソッドにUserInfo
パラメーターオブジェクトのタイプを直接注入できます。
// http://localhost:8080/test/user/current/info
@GetMapping("/user/current/info")
public UserInfo getUserInfo(@AuthenticationPrincipal UserInfo userInfo) {
System.out.println("user id = " + userInfo.getId());
System.out.println("user nickname = " + userInfo.getNickname());
return userInfo;
}