The project obtains the information of the currently logged-in user through Spring Security (6)

20. Use the controller to forward the registration page

Move the register.html file registered by the user to the templates folder.

In SystemControlleradding:

@GetMapping("/register.html")
public String register() {
    
    
    return "register";
}

In SecurityConfig, add the registration-related "/register.html"and "/portal/user/student/register"these 2 URLs to the whitelist.

21. Handling user permissions

21.1. Completion: Assign roles when students register

In the "student registration" business, the id of the newly inserted user data should be obtained in time, and the user id and role id (the id of the student role is fixed to 2) should be inserted into the user_roledata table to record the newly registered student Character.

First UserServiceImpladd in:

@Autowired
private UserRoleMapper userRoleMapper;

Then, in the original "student registration" business, finally add:

// 向“用户角色表”中插入数据,为当前学生账号分配角色
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("注册失败!服务器忙,请稍后再次尝试!");
}

After completion, you need to add a @Transactionalnote before the business method of "student registration" to enable the transaction.

Regarding transactions, it is a mechanism provided by the database, which can ensure that a series of write operations (including insertion, deletion, and modification) will either succeed or fail!

Assuming there are data:

account number Balance
Cang Song 1000
Guobin 8000

If you want to realize "Guobin transfers 5000 yuan to Cangsong", the data operations that need to be performed are:

UPDATE 账户表 SET 余额=余额-5000 WHERE 账号='国斌';
UPDATE 账户表 SET 余额=余额+5000 WHERE 账号='苍松';

In case, during the execution process, due to some uncontrollable factors, the previous SQL statement is successfully executed, but the latter SQL statement cannot be executed, which will lead to data security issues. In this case, you need to use transactions. If the two SQL statements are executed successfully, they are successfully completed. If any one of the SQL statements is executed incorrectly, as long as you ensure that all are failed (even if some SQL statements have been successfully executed before, also Will fail) and data security will not be affected!

Based on Spring JDBC transaction processing, you only need to add @Transactionalannotations before the business method . The processing mechanism is roughly:

try {
	开启事务:BEGIN
	执行若干个数据访问操作(增、删、改、查)
	提交事务(保存数据):COMMIT
} catch (RuntimeException e) {
	回滚事务:ROLLBACK
}

Therefore, in order to ensure the effective execution of the transaction mechanism, you must:

  • If a business involves 2 or more write operations (for example, 2 INSERT operations, or 1 INSERT plus 1 DELETE, etc.), you must add an @Transactionalannotation before the business method to enable the transaction;
  • Every time the write operation of the persistence layer is called, the returned "number of rows affected" must be obtained in time, and the return value must be determined whether it is consistent with the expected value. If it does not, it must throw RuntimeExceptionor its descendant class exception object !

When developing a project, the reason why business exceptions need to be inherited from RuntimeExceptionis because:

  • Easy to write code to avoid using exception requires strict syntax for declaring thrown or caught, because RuntimeExceptionits descendants, is not mandatory for an exception try...catchor throw/throws, and, after the service layer throw an exception, the controller layer is also all throw again, Handed over to a unified exception handling mechanism for processing;
  • Ensure the normal use of the transaction mechanism.

In addition, @Transactionalannotations can also be added before the declaration of the business class, which will make all the methods in the current class run based on the transaction mechanism. However, this is generally not necessary, so it is not recommended!

You should also understand: ACID characteristics of transactions, transaction isolation, and transaction propagation.

21.2. Obtaining permissions when handling logins

In the above registration process, "Assign Role" is added, and each role corresponds to certain permissions, so the process of "Assigning Role" is the process of "Assigning Authority"! When the user logs in, the user's permissions should be read to complete the authorization of Spring Security in the verification process to ensure that the correct judgment can be given when certain accesses are made in the future, so that certain users can perform certain operations. Other users may not be able to perform these operations because they do not have permission!

First, you need to implement the function of "query the user's permissions based on user id". The SQL statement that needs to be executed is roughly:

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;

PermissionMapperAdd an abstract method to the persistence layer interface that handles permission data :

/**
 * 查询某用户的权限
 * @param userId 用户的id
 * @return 该用户的权限的列表
 */
List<Permission> selectByUserId(Integer userId);

Then, PermissionMapper.xmlconfigure the SQL statement corresponding to the above abstract method in:

<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>

After completion, create a PermissionMapperTeststest class at the test location , write and execute unit tests:

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

}

Next, in the business of processing login, that is UserServiceImpl, add it first:

@Autowired
private PermissionMapper permissionMapper;

And login()add in the method:

// 权限字符串数组
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();

Due to the modification of the registered business (the "Assign role to student account" has just been added), the original test data may not be available. In order to facilitate subsequent test use, all the original data should be cleared first:

TRUNCATE user;

And register some new accounts again through the registration business or registration page.

At the same time, some data should be identified as teachers:

UPDATE user SET type=1 WHERE id IN (1, 2, 3);

In the user role assignment table, clear the original data, and change the roles of some accounts to administrators and teachers:

-- 清空用户角色分配表
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. Get the information of the currently logged-in user through Spring Security

After the user successfully logs in, he needs to obtain the user's information to perform subsequent operations, such as obtaining a user's authority, obtaining a user's question list, obtaining a user's personal information, and so on.

Spring Security provides a simple way to obtain the information of the currently logged in user. In the method of processing the request of the controller, add Authenticationtype parameters or add Principaltype parameters to obtain the information of the currently logged in user, for example:

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

The output results of the above two methods are exactly the same, because they Authenticationare inherited from Principal, when the Spring MVC framework tries to inject parameter values, the same object is injected!

The above methods output a lot of content, and you can also use the following methods to obtain user information:

// http://localhost:8080/test/user/current/details
@GetMapping("/user/current/details")
public UserDetails getUserDetails(@AuthenticationPrincipal UserDetails userDetails) {
    
    
    return userDetails;
}

23. Extend UserDetails

After the above injection @AuthenticationPricipal UserDetails userDetails, the user's information can be obtained, but the information encapsulated in the object may not be enough to meet the programming needs, such as no user's idor some other attributes! If these attributes need to exist, you need to customize the class and extend it UserDetails!

cn.tedu.straw.portal.securityCreate a UserInfoclass under the package , inherit from the Userclass, and declare the required custom attributes in this class:

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

}

Note: Since Userthere is no parameterless construction method in the parent class , a construction method with matching parameters needs to be added after inheritance!

Note: Since the parent Usernon-parametric methods do not exist in the configuration, it can not be used in the Lombok @Dataannotation can be added on demand @Setter, @Getterand other annotations.

Then, when processing user login in the business layer, use the UserInfotype of object created above as the return value object:

// 组织“用户详情”对象
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;

In the future, when you need to obtain the information of the currently logged in user, you can directly inject the UserInfotype of parameter object into the method of processing the request of the controller :

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

Guess you like

Origin blog.csdn.net/qq_44273429/article/details/107601658