Series directory

SpringSecurity rights management system actual combat – one, project introduction and development environment preparation

SpringSecurity rights management system practice – two, log, interface documents and other implementation

SpringSecurity rights management system practice – three, the main page and interface implementation

SpringSecurity rights management system actual combat – four, integration of SpringSecurity (part 1)

SpringSecurity rights management system practice – five, integration of SpringSecurity (under)

SpringSecurity authority management system combat – six, SpringSecurity integration JWT

SpringSecurity rights management system practice – seven, deal with some problems

SpringSecurity rights management system practice – eight, AOP record user logs, abnormal logs

SpringSecurity rights management system actual combat – nine, data rights configuration

preface

In the previous article, SpringSecurity integrated half of it, and this time completes the other half, so the serial number of this article continues.

Customize user information

Previously we logged in using either the specified username and password or springsecurity’s default username and printed password. To connect to the custom database, we just need to implement a custom UserDetailsService.

Let’s create a new JwtUserDto that inherits UserDetails and implements its methods

@Data
@AllArgsConstructor
public class JwtUserDto implements UserDetails {
    // User data
    private MyUser myUser;
	// Set of user permissions
    @JsonIgnore
    private List<GrantedAuthority> authorities;
		
    public List<String> getRoles(a) {
        return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
    }
	// Encrypted password
    @Override
    public String getPassword(a) {
        return myUser.getPassword();
    }
	/ / user name
    @Override
    public String getUsername(a) {
        return myUser.getUserName();
    }
	// Whether to expire
    @Override
    public boolean isAccountNonExpired(a) {
        return true;
    }
	// Whether to lock
    @Override
    public boolean isAccountNonLocked(a) {
        return true;
    }
	// Whether the credentials are expired
    @Override
    public boolean isCredentialsNonExpired(a) {
        return true;
    }
	// Whether it is available
    @Override
    public boolean isEnabled(a) {
        return myUser.getStatus() == 1 ? true : false; }}Copy the code

Define a user-defined UserDetailsServiceImpl implementation UserDetailsService

@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserService userService;
    @Autowired
    private MenuDao menuDao;
    @Override
    public JwtUserDto loadUserByUsername(String userName) throws UsernameNotFoundException {
        MyUser user = userService.getUser(userName);// Get the user based on the user name
        if (user == null) {throw new UsernameNotFoundException("User name does not exist");// This exception must be thrown
        }else if (user.getStatus().equals(MyUser.Status.LOCKED)) {
            throw new LockedException("User is locked. Please contact administrator.");
        }
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        List<MenuIndexDto> list = menuDao.listByUserId(user.getId());
        List<String> collect = list.stream().map(MenuIndexDto::getPermission).collect(Collectors.toList());
        for (String authority : collect){
            if(! ("").equals(authority) & authority ! =null){
                GrantedAuthority grantedAuthority = newSimpleGrantedAuthority(authority); grantedAuthorities.add(grantedAuthority); }}// Add user permissions to the GrantedAuthority collection
        JwtUserDto loginUser =new JwtUserDto(user,grantedAuthorities);
        returnloginUser; }}Copy the code

Mybatis data is null, if you never modify the data, it is null. If you modify it and then delete it, it will be null.

The listByUserId method in meudao

 @Select("SELECT DISTINCT sp.id,sp.parent_id,sp.name,sp.icon,sp.url,sp.type,sp.permission " + "FROM my_role_user sru " + "INNER JOIN my_role_menu srp ON srp.role_id = sru.role_id " + "LEFT JOIN my_menu sp ON srp.menu_id = sp.id " + "WHERE " + "sru.user_id = #{userId}")
    @Result(property = "title",column = "name")
    @Result(property = "href",column = "url")
    List<MenuIndexDto> listByUserId(@Param("userId")Integer userId);
Copy the code

Eight, encryption,

Let’s talk about the importance of encryption.

In 2011, a Chinese developer community (CSDN?) was attacked database, more than 6 million user accounts stored in plaintext were disclosed, and a large number of user privacy was leaked.

This is an old meme. In almost every blog post about the importance of encryption, the CSDN issue is brought up.

So why is password encryption important? If your password is also in the hands of a hacker when your database is compromised, then even if you fix the problem, the hacker still has the user’s password.

So we need to avoid this problem as much as possible at the beginning of the system development.

So with all that said, how do you encrypt?

In fact, SpringSecurity has built-in password encryption mechanism, just need to implement a PasswordEncoder interface.

Take a look at the source code

public interface PasswordEncoder {
    String encode(CharSequence var1);

    boolean matches(CharSequence var1, String var2);

    default boolean upgradeEncoding(String encodedPassword) {
        return false; }}Copy the code
  • Encode (): Parses parameters according to specific parsing rules.
  • Matches () verifies that the encoded password retrieved from the store matches the original password submitted after encoding. Return true if the passwords match; If there is no match, return false.
  • UpgradeEncoding () : Returns true if the parsed password can be parsed again with a more secure result, false otherwise. False is returned by default.

The first parameter indicates the password that needs to be resolved. The second parameter indicates the stored password.

Spring Security also has several commonly used PasswordEncoder interfaces built in, with BCryptPasswordEncoder being the official recommendation

. Let’s configure it. Add the following code in the SpringConfig category.

	@Autowired
    private UserDetailsService userDetailsService;
	@Bean
    public PasswordEncoder passwordEncoder(a){
        return new BCryptPasswordEncoder();
    }
	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }// Customize the userDetailsService encryption
Copy the code

The console will no longer print the password. Now you need to enter the username and password in the database to log in.

Access to user information

When we drew the menu, we wrote out the user ID. Now we’re going to get the user information from SpringSecurity.

There are two ways to retrieve logged-in user information, one from the session, and the other provided by SpringSecurity. Choose the latter method here.

We can obtain the information of the user after login through the following methods (there are other methods such as obtaining the login IP, which will not be described in detail).

SecurityContextHolder.getContext().getAuthentication().getPrincipal()
Copy the code

Let’s convert the type

JwtUserDto jwtUserDto = (JwtUserDto)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Copy the code

Print jwtUserDto and see that we actually got the user’s information

So let’s rewrite the method of getting the menu by user ID

 	@GetMapping(value = "/index")
    @ResponseBody
    @apiOperation (value = "get menu by user ID ")
    public List<MenuIndexDto> getMenu(a) {
        JwtUserDto jwtUserDto = (JwtUserDto)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Integer userId = jwtUserDto.getMyUser().getId();
        return menuService.getMenu(userId);
    }
Copy the code

Delete the userId before writing the front end dead. Now we can automatically draw menus based on the login user.

The user has the admin permission

A user with common rights

Ten, authorization,

At present, we only draw the interface that users with different permissions can operate, but there is no real permission control.

Previously in 7, we put the set of permissions owned by each user into the GrantedAuthority collection

In the user information printed previously, you can see that the user has the authorities

SpringSecurity automatically does the permissions for us. All we need to do is add permission flags to the methods that need permission control.

For example, the permission id of a user is user:list

We simply add @preauthorize (“hasAnyAuthority(‘user:list’)”) to the related interfaces

	@GetMapping("/index")
    @PreAuthorize("hasAnyAuthority('user:list')")
    public String index(a){
        return "system/user/user";
    }
    @GetMapping
    @ResponseBody
    @apiOperation (value = "user list ")
    @PreAuthorize("hasAnyAuthority('user:list')")
    public Result<MyUser> userList(PageTableRequest pageTableRequest, UserQueryDto userQueryDto){
        pageTableRequest.countOffset();
        return userService.getAllUsersByPage(pageTableRequest.getOffset(),pageTableRequest.getLimit(),userQueryDto);
    }
Copy the code

Now we log in ordinary user to operate the related interface, found an error

Console printing

Next, we need to modify all interfaces, add corresponding annotations on the interfaces that need permission control, please do it yourself.

Custom exception handling

Although now the function has been implemented, although the user cannot access the function without permission, but the exception is not handled. If you click, the user will see a string of error messages if the front end does not intercept incorrectly, which is very unfriendly and can cause stress to the server.

We just need to catch the exception shown above in the global exception handling class we created earlier.

	@ExceptionHandler(AccessDeniedException.class)
    public Result handleAuthorizationException(AccessDeniedException e)
    {
        log.error(e.getMessage());
        return Result.error().code(ResultCode.FORBIDDEN).message("No permission, please contact the administrator for authorization.");
 }
Copy the code

Restarting the project and writing the rules on the front end will be user friendly

12. Custom log out

By default, SpringSecurity registers a /logout route that can be used to logout login states, including Session and remember-me.

We can define the rule directly in Configure of SpringSecurityConfig, similar to formLogin. You can also customize a LogoutHadnler (see this article)

Now that some of SpringSecurity’s common features are in place, we’ll integrate JWT for stateless login in the next section

This seriesgiteeandgithubSynchronous update in