Implementing rights management system with spring security

For space reasons, please refer to this article to realize the login function first https://blog.csdn.net/grd_java/article/details/121925792

1, Database tables, and entity classes

Because the front end is written by VUE, components and paths need to be saved




  1. sql statements for all libraries

2, Menu management TODO

final result

1. Entity class

Through the mybatis plus plug-in code generator, it is automatically generated, and then a field children is added

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableField;
import java.io.Serializable;
import java.util.List;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * <p>
 * 
 * </p>
 *
 * @author testjava
 * @since 2021-12-14
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="DdMenu object", description="")
public class DdMenu implements Serializable {

    private static final long serialVersionUID=1L;

    @ApiModelProperty(value = "id")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @ApiModelProperty(value = "Parent menu id")
    private Integer pid;

    @ApiModelProperty(value = "menu name")
    private String name;

    @ApiModelProperty(value = "Menu type (catalog, button)")
    private String type;

    @ApiModelProperty(value = "Menu permissions")
    private String permission;

    @ApiModelProperty(value = "url")
    private String url;

    @ApiModelProperty(value = "path")
    private String path;

    @ApiModelProperty(value = "assembly")
    private String component;

    @ApiModelProperty(value = "Icon")
    @TableField("iconCls")
    private String iconCls;

    @ApiModelProperty(value = "Keep active")
    @TableField("keepAlive")
    private Boolean keepAlive;

    @ApiModelProperty(value = "Permission required")
    @TableField("requireAuth")
    private Boolean requireAuth;

    @ApiModelProperty(value = "Enable")
    private Boolean enabled;

    @ApiModelProperty(value = "Submenu")
    @TableField(exist = false)//Tell Mybatis that this field does not exist in the table, otherwise it will be found in the database during operation
    private List<DdMenu> children;
}

2. Redis cache

  1. Start redis
  2. Add dependency
<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--commons-pool2 Object pool dependency-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
  1. redis configuration
spring:
  redis: #redis configuration
    host: 127.0.0.1 #Your redis address
    port: 6379 #Port number
    database: 0
    timeout: 1800000
    lettuce:
      pool:
        max-active: 1024 # maximum connection
        max-wait: -1 #Maximum blocking waiting time (negative number indicates no limit)
        max-idle: 200 #Maximum idle connections
        min-idle: 5 #Minimum idle connection
  1. Redis configuration class
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * redis Configuration class
 */
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        //String type key sequencer
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //String type value sequencer
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        //Hash type key sequencer
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //Hash type value sequencer
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        //Configuration factory
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}

3. Write logic and query menu

  1. controller
import com.dd.security.entity.DdMenu;
import com.dd.security.service.DdMenuService;
import com.dd.security.service.DdUserService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * <p>
 *  Front end controller
 * </p>
 *
 * @author testjava
 * @since 2021-12-14
 */
@RestController
@RequestMapping("/security/dd-menu")
public class DdMenuController {
    @Autowired
    private DdMenuService ddMenuService;

    @ApiOperation(value = "By user id Query menu list")
    @GetMapping("/menu")
    public List<DdMenu> getMenusByUserId(){
        return ddMenuService.getMenusByUserId();
    }
}
  1. service
import com.dd.security.entity.DdMenu;
import com.dd.security.entity.DdUser;
import com.dd.security.mapper.DdMenuMapper;
import com.dd.security.service.DdMenuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.awt.*;
import java.util.List;

/**
 * <p>
 *  Service implementation class
 * </p>
 *
 * @author testjava
 * @since 2021-12-14
 */
@Service
public class DdMenuServiceImpl extends ServiceImpl<DdMenuMapper, DdMenu> implements DdMenuService {

    @Autowired
    private DdMenuMapper ddMenuMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * Get menu based on user id
     */
    @Override
    public List<DdMenu> getMenusByUserId() {
        //Get UserDetails object from Security global context
        DdUser user = (DdUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        //Get redis object
        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
        //First query from redis
        List<DdMenu> menus = (List<DdMenu>) valueOperations.get("menu_" + user.getId());
        //If not in redis, query the mysql database
        if(CollectionUtils.isEmpty(menus)){
            menus = ddMenuMapper.getMenusByUserId(user.getId());
            //Put the queried content in reids
            valueOperations.set("menu_" + user.getId(),menus);
        }
        //Return results
        return menus;
    }
}

  1. mapper
select
	distinct
	m1.*,
	m2.id as id2,
	m2.pid as pid2,
	m2.`name` as name2,
	m2.type as type2,
	m2.permission as permission2,
	m2.url as url2,
	m2.path as path2,
	m2.component as component2,
	m2.iconCls as iconCls2,
	m2.keepAlive as keepAlive2,
	m2.requireAuth as requireAuth,
	m2.enabled as enabled2
from
	dd_menu as m1 
inner join
	dd_menu as m2 #Self correlation
on
	m1.id = m2.pid
inner join
	dd_role_menu as rm
on
	rm.mid = m2.id
inner join
	dd_user_role as ur
on
	ur.rid = rm.rid
where
	ur.uid = 1


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dd.security.mapper.DdMenuMapper">

    <!-- DdMenu Mapping object-->
    <resultMap id="BaseResultMap" type="com.dd.security.entity.DdMenu">
        <id column="id" property="id" />
        <result column="pid" property="pid" />
        <result column="name" property="name" />
        <result column="type" property="type" />
        <result column="permission" property="permission" />
        <result column="url" property="url" />
        <result column="path" property="path" />
        <result column="component" property="component" />
        <result column="iconCls" property="iconCls" />
        <result column="keepAlive" property="keepAlive" />
        <result column="requireAuth" property="requireAuth" />
        <result column="enabled" property="enabled" />
    </resultMap>
    <!--Submenu, inherit above DdMenu Mapping object-->
    <resultMap id="Menus" type="com.dd.security.entity.DdMenu" extends="BaseResultMap">
        <collection property="children" ofType="com.dd.security.entity.DdMenu">
            <id column="id2" property="id" />
            <result column="pid2" property="pid" />
            <result column="name2" property="name" />
            <result column="type2" property="type" />
            <result column="permission2" property="permission" />
            <result column="url2" property="url" />
            <result column="path2" property="path" />
            <result column="component2" property="component" />
            <result column="iconCls2" property="iconCls" />
            <result column="keepAlive2" property="keepAlive" />
            <result column="requireAuth2" property="requireAuth" />
            <result column="enabled2" property="enabled" />
        </collection>
    </resultMap>

    <select id="getMenusByUserId" resultMap="Menus">
        select
            distinct
            m1.*,
            m2.id as id2,
            m2.pid as pid2,
            m2.`name` as name2,
            m2.type as type2,
            m2.permission as permission2,
            m2.url as url2,
            m2.path as path2,
            m2.component as component2,
            m2.iconCls as iconCls2,
            m2.keepAlive as keepAlive2,
            m2.requireAuth as requireAuth,
            m2.enabled as enabled2
        from
            dd_menu as m1
                inner join
            dd_menu as m2
            on
                m1.id = m2.pid
                inner join
            dd_role_menu as rm
            on
                rm.mid = m2.id
                inner join
            dd_user_role as ur
            on
                ur.rid = rm.rid
        where
            ur.uid = #{id}
    </select>
</mapper>

2, url role permissions

If we only control the menu and the user logs in to the management system, he can only see the menu corresponding to his role permissions. However, if the user obtains the url corresponding to our background interface by some means, even if the front end can't see the corresponding menu and button, he can't access the back-end interface through events. You can also directly enter the url through the browser address bar, because it has been logged in. At this time, you can directly access the url. Although we don't want him to have access to these interfaces, so that he can't see the corresponding menus and buttons, he can only watch him get data at this time
How to deal with this situation? Our menu table has a url field that records the url address
  1. In the menu entity class, we add the roles and permission attributes required to access this url and menu
  2. It is specified that the URLs in the menu table need specific roles and permissions to access. The remaining URLs can be allowed to have no roles or LOGIN_ROLE user access to the default role.
  3. When the user accesses the back end, first filter the request and judge whether the user requests the url of the menu table. If so, judge whether the user has the corresponding permission. If the user has no permission to intercept the request, the return permission is insufficient. If it is not a menu table url, add the default role login for it_ Role role
Final effect



Role required for access_ The path of the admin role can be accessed normally

Insufficient permission to access URLs requiring other roles

Accessing paths that do not require authorization will not trigger filtering, and will not intercept the permissions required to determine the url

1. url permissions: set the url accessed by the user in the global. What permissions are required

1. Obtain the role permissions required for each menu

The implementation obtains the menu list according to the role, that is, which roles have permissions for each menu

  1. Modify the menu entity class and add role attributes
  2. service interface

  3. mapper

<!--Submenu, inherit above DdMenu Mapping object-->
<resultMap id="MenusWithRoler" type="com.dd.security.entity.DdMenu" extends="BaseResultMap">
    <collection property="roles" ofType="com.dd.security.entity.DdRole">
        <id column="rid" property="id" />
        <result column="rname" property="name" />
        <result column="rnameZh" property="nameZH" />
    </collection>
</resultMap>
<select id="getMenusWithRole" resultMap="MenusWithRoler">
    select
        m.*,
        r.id as rid,
        r.`name` as rname,
        r.name_zh as rnameZh
    from
        dd_menu as m
    inner join
        dd_role_menu as rm
    on
        m.id = rm.mid
    inner join
        dd_role as r
    on
        r.id = rm.rid
    order by m.id
</select>

2. The spring security filter intercepts the request, obtains the url, and determines which role permissions are required for the url

Forgot to add @ Component annotation in the screenshot

import com.dd.security.entity.DdMenu;
import com.dd.security.entity.DdRole;
import com.dd.security.service.DdMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.util.AntPathMatcher;

import java.util.Collection;
import java.util.List;

/**
 * Permission control
 * Analyze the required roles according to the request url
 */
 @Component
public class CustomFilter implements FilterInvocationSecurityMetadataSource {
    @Autowired
    private DdMenuService ddMenuService;


    private AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        //Get FilterInvocation object
        FilterInvocation filterInvocation = (FilterInvocation) o;
        //Get request url
        String requestUrl = filterInvocation.getRequestUrl();
        //Get the corresponding role of each menu, that is, the url. Which roles have permissions
        List<DdMenu> menusWithRole = ddMenuService.getMenusWithRole();
        //Traverse menu
        for (DdMenu m : menusWithRole){
            //If the user request url matches the url in the menu
            if(antPathMatcher.match(m.getUrl(),requestUrl)){
                //Streaming programming + lambda expression: Class Name:: method name, method reference, DdRole::getName refers to the getName method that references the DdRole class
                //Class name:: new construction method reference, String []: new refers to the construction method referencing String [], and constructs an array
                //To obtain the role list, that is, the url requested by the user, you need to have the following roles
                String[] roles = m.getRoles().stream().map(DdRole::getName).toArray(String[]::new);

                //org.springframework.security.access.SecurityConfig;
                //Put our role in Security
                return SecurityConfig.createList(roles);
            }
        }
        //If the user request url is not the url in the menu, give the default role role_ Login (login role), that is, you must have a login role to access
        return SecurityConfig.createList("ROLE_LOGIN");
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return false;
    }
}

2. Obtain the user role and judge whether the user role permission can access the url

1. Get roles

  1. Entity class
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="DdUser object,use Spring Security The framework should inherit UserDetails Interface, implement the method, and change the return value to true", description="")
public class DdUser implements UserDetails {
    private static final long serialVersionUID=1L;

    @ApiModelProperty(value = "id")
    @TableId(value = "id", type = IdType.ID_WORKER_STR)
    private Integer id;
    private String username;
    private String password;

    @ApiModelProperty(value = "User role")
    @TableField(exist = false)
    private List<DdRole> roles;
    /**
     * All permissions
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        List<SimpleGrantedAuthority> authorities =
                roles.stream()
                        //Traverse each role into the authority character object specified by Security,
                        // Like role_ Admin should be encapsulated as new SimpleGrantedAuthority("ROLE_admin")
                .map(role -> new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());//Then return to list
        return authorities;//Return role permissions to
    }

    /**
     * Whether the account has expired
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * Is the account locked
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * Is the voucher (password) expired
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * Enable
     * @return
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}
  1. service

  2. mapper

<!--/**
 * Obtain user owned roles according to user id
 */-->
<select id="getRolesByUserId" resultType="com.dd.security.entity.DdRole" parameterType="java.lang.Integer">
    select
        r.id as id,
        r.name as name,
        r.name_zh as nameZh
    from
        dd_role as r
            inner join
        dd_user_role as ur
        on
            r.id = ur.rid
    where
        ur.uid = #{userId}
</select>
  1. Modify and obtain user information
  2. Modify custom UserDetailsService
 /**
  * Configure UserDetailsService
  */
 @Bean
 @Override
 public UserDetailsService userDetailsService() {
     return username->{
         DdUser user = ddUserService.getLoginInfoByUsername(username);
         if(user == null){
             throw new UsernameNotFoundException("Username or password incorrect ");
         }
         List<DdRole> rolesByUserId = ddRoleService.getRolesByUserId(user.getId());
         user.setRoles(rolesByUserId);
         return user;
     };
 }

2. Use the security filter to determine whether the user role can be accessed

Forgot to add @ Component annotation in the screenshot

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

/**
 * Permission control
 * Judge user role
 */
import java.util.Collection;
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
    /**
     *
     * @param authentication Current access user
     * @param o
     * @param collection Collection<ConfigAttribute> We configured it in the previous filtercustomefilter implements filterinvocationsecuritymetadatasource
     * @throws AccessDeniedException
     * @throws InsufficientAuthenticationException
     */
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {

        //Traverse ConfigAttribute
        for(ConfigAttribute configAttribute : collection){
            //Obtain the current access url and the required role permissions. These values are set in CustomFilter
            String needRole = configAttribute.getAttribute();
            //Determine whether the url can be accessed by logging in, and then set it in CustomFilter
            if("ROLE_LOGIN".equals(needRole)){
                //If the current user is anonymous (not logged in), throw an exception and let the user log in
                if(authentication instanceof AnonymousAuthenticationToken){
                    throw new AccessDeniedException("Not logged in, please log in!!!");
                }else{
                    return;
                }
            }
            //If the current url is not logged in, you can access it
            //Judge whether there is a required role in the current user's GrantedAuthority. If so, release it
            //If the user does not have a corresponding role, it will not be released. At the same time: users who do not log in will not be released because they do not have a role
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for(GrantedAuthority authority:authorities){
                if(authority.getAuthority().equals(needRole)){
                    return;
                }
            }
        }
        throw new AccessDeniedException("Insufficient permissions, please contact the administrator!!!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return false;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return false;
    }
}

3. Configure spring security to make the filter effective

Note: these filters will not filter the paths released in the Security configuration class

  1. Introduce two filters
  2. Configure two filters for dynamic permissions
@Override
protected void configure(HttpSecurity http) throws Exception {

    //csrg is not required to use JWT
    http.csrf().disable()
            //Using Token, session is not required
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()//Next, configure authorization
            .authorizeRequests()
            //The following authorization is configured in the configure (Web Security Web) method, which is not required here
//                . antMatchers("/login","/logout").permitAll() / / allow access to / login, and / logout requests can pass without authentication
            .anyRequest().authenticated()//In addition to the above configuration, all the remaining requests are intercepted and can only be accessed after authentication
            .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {//Dynamic permission configuration
                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                    o.setAccessDecisionManager(customUrlDecisionManager);//Determine whether the user role can access the url Filter
                    o.setSecurityMetadataSource(customFilter);//Analyze the required role filter based on the request url
                    return o;
                }
            })
            .and()//Next, configure the cache
            .headers()
            .cacheControl()
            ;
    //Add JWT login authorization filter interceptor
    http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    //Add custom unauthorized and unregistered results. The front and back ends are separated, and the status code needs to be returned
    http.exceptionHandling()
            .accessDeniedHandler(restAccessDeniedHandler)
            .authenticationEntryPoint(restAuthorizationEntryPoint);

}

4. Test


3, Implementation of permission group function

1. Role

The ROLE of spring security must have a ROLE_ Prefix, otherwise it will not be captured by spring security

Correct role name: ROLE_admin,ROLE_adsfasdf
Bad role name: r_admin,admin,ROLE_

  1. Therefore, when we add a ROLE, we should judge whether the user uses ROLE_ At the beginning, the addition logic is not added, but directly executed
  1. Controller
import com.dd.common_utils.Result;
import com.dd.security.entity.DdRole;
import com.dd.security.service.DdRoleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/security/dd-role")
public class DdRoleController {
    @Autowired
    private DdRoleService ddRoleService;

    @ApiOperation("Get all roles")
    @GetMapping("/")
    public Result getAllRoles(){
        List<DdRole> list = ddRoleService.list();
        if(list.isEmpty()){
            return Result.error().message("No role information obtained!!!");
        }
        return Result.ok().data("RoleAllList",list);
    }
    @ApiOperation("Add role")
    @PostMapping("/")
    public Result addRole(@RequestBody DdRole ddRole){
        //If the ROLE name is not ROLE_ First, add
        if(!ddRole.getName().startsWith("ROLE_")){
            ddRole.setName("ROLE_"+ddRole.getName());
        }
        if(ddRoleService.save(ddRole)){
            return Result.ok().message("Added successfully");
        }
        return Result.error().message("Add failed");
    }

    @ApiOperation("Delete role")
    @DeleteMapping("/role/{id}")
    public Result deleteRole(@PathVariable(value = "id",name = "id") Integer id){
        if(ddRoleService.removeById(id)){
            return Result.ok().message("Delete succeeded");
        }
        return Result.error().message("Deletion failed");
    }
}

2. Menu

  1. controller
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dd.common_utils.Result;
import com.dd.security.entity.DdMenu;
import com.dd.security.entity.DdRoleMenu;
import com.dd.security.service.DdMenuService;
import com.dd.security.service.DdRoleMenuService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 *  Front end controller
 * </p>
 *
 * @author testjava
 * @since 2021-12-14
 */
@RestController
@RequestMapping("/security/dd-menu")
public class DdMenuController {
    @Autowired
    private DdMenuService ddMenuService;
    @Autowired
    private DdRoleMenuService ddRoleMenuService;

    @ApiOperation(value = "By user id Query menu list")
    @GetMapping("/menu")
    public List<DdMenu> getMenusByUserId(){
        return ddMenuService.getMenusByUserId();
    }

    @ApiOperation(value = "Query all menus")
    @GetMapping("/menus")
    public Result getMenus(){
        List<DdMenu> list = ddMenuService.list();
        if(list.isEmpty()){
            return Result.error().message("No menu!!");
        }
        return Result.ok().data("menuAllList",list);
    }

    @ApiOperation(value = "According to role id Query menu id")
    @GetMapping("/menuIdByRoleId/{rid}")
    public Result getMenuIdByRoleId(@PathVariable Integer rid){
        List<Integer> mids = ddRoleMenuService.list(new QueryWrapper<DdRoleMenu>().eq("rid", rid))
                .stream().map(DdRoleMenu::getMid).collect(Collectors.toList());
        if(mids.isEmpty()){
            return Result.error().message("Failed to get menu");
        }
        return Result.ok().data("menuId",mids);
    }

    @ApiOperation(value = "According to role id Query menu")
    @GetMapping("/menuByRoleId/{rid}")
    public Result getMenuByRoleId(@PathVariable Integer rid){
        List<DdMenu> list = ddMenuService.getMenuByRoleId(rid);
        if(list.isEmpty()){
            return Result.error().message("No menu!!");
        }
        return Result.ok().data("menuList",list);
    }

    @ApiOperation(value = "Update role menu")
    @PutMapping("/")
    public Result updateMenuRole(Integer rid,Integer[] mids){
        return ddMenuService.updateMenuRole(rid,mids);
    }
}
  1. service

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dd.common_utils.Result;
import com.dd.security.entity.DdMenu;
import com.dd.security.entity.DdRoleMenu;
import com.dd.security.entity.DdUser;
import com.dd.security.mapper.DdMenuMapper;
import com.dd.security.mapper.DdRoleMenuMapper;
import com.dd.security.service.DdMenuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dd.security.service.DdRoleMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.awt.*;
import java.util.List;

/**
 * <p>
 *  Service implementation class
 * </p>
 *
 * @author testjava
 * @since 2021-12-14
 */
@Service
public class DdMenuServiceImpl extends ServiceImpl<DdMenuMapper, DdMenu> implements DdMenuService {

    @Autowired
    private DdMenuMapper ddMenuMapper;
    @Autowired
    private DdRoleMenuMapper ddRoleMenuMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * Get menu based on user id
     */
    @Override
    public List<DdMenu> getMenusByUserId() {
        //Get UserDetails object from Security global context
        DdUser user = (DdUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        //Get redis object
        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
        //First query from redis
        List<DdMenu> menus = (List<DdMenu>) valueOperations.get("menu_" + user.getId());
        //If not in redis, query the mysql database
        if(CollectionUtils.isEmpty(menus)){
            menus = ddMenuMapper.getMenusByUserId(user.getId());
            //Put the queried content in reids
            valueOperations.set("menu_" + user.getId(),menus);
        }
        //Return results
        return menus;
    }

    /**
     * Get menu list by role
     */
    @Override
    public List<DdMenu> getMenusWithRole() {
        return ddMenuMapper.getMenusWithRole();
    }

    @Override
    public List<DdMenu> getMenuByRoleId(Integer rid) {
        return ddMenuMapper.getMenuByRoleId(rid);
    }

    /**
     * Update role menu
     * Delete all menus of the current role before updating
     * @param rid Role id
     * @param mids Menu id to update
     */
    @Override
    @Transactional//When you write updates yourself, you must add transactions
    public Result updateMenuRole(Integer rid, Integer[] mids) {
        //Delete it all first, or you'll have to judge it again and again. It's too expensive
        ddRoleMenuMapper.delete(new QueryWrapper<DdRoleMenu>().eq("rid", rid));
        //If the menu id to be updated is empty, return directly
        if(null == mids||mids.length == 0){
            return Result.ok().message("Menu updated successfully");
        }
        //If the menu id is passed, it will be updated
        ddMenuMapper.insertRecord(rid,mids);
        return null;
    }
}
  1. mapper

    <!--Update role menu-->
    <insert id="insertRecord">
        insert into dd_role_menu(rid,mid) values
        <foreach collection="mids" item="mid" separator=",">
            (#{rid},#{mid})
        </foreach>
    </insert>

3. Users

  1. controller

import com.dd.common_utils.Result;
import com.dd.security.entity.DdMenu;
import com.dd.security.entity.DdUser;
import com.dd.security.service.DdUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.parameters.P;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * <p>
 *  Front end controller
 * </p>
 *
 * @author testjava
 * @since 2021-12-13
 */
@RestController
@Api("Do not carry when passing query criteria security Field, only carry DdUser Fields of the object itself, JSON")
@RequestMapping("/security/dd-user-role")
public class DdUserRoleController {
    @Autowired
    private DdUserService ddUserService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @ApiOperation("Get all operators and their roles and permissions,Do not carry when passing query criteria security Field, only carry DdUser Fields of the object itself, JSON")
    @PostMapping("/")
    public Result getAllUser(@RequestBody DdUser ddUser){
        List<DdUser> list = ddUserService.getAllUser(ddUser);
        if(list.isEmpty()){
           return Result.error().message("No user information obtained!!!");
        }
        return Result.ok().data("allUserList",list);
    }

    @ApiOperation("Update user")
    @PutMapping("/")
    public Result updateUser(@RequestBody DdUser ddUser){
        ddUser.setPassword(null);//Direct password change is not allowed

        if(ddUserService.updateById(ddUser)){
          return Result.ok().message("Modify user successfully!!!");
        }
        return Result.error().message("Modification failed!!!");
    }

    @ApiOperation("Update user owned roles")
    @PutMapping("/updateUserRole")
    public Result updateUserRole(Integer uid,Integer[] rids){
        return ddUserService.updateUserRole(uid,rids);
    }

    @ApiOperation("Change Password")
    @PutMapping("/updatePasswordById/{id}/{password}")
    public Result updatePassword(@PathVariable Integer id, @PathVariable String password){
        String encode = passwordEncoder.encode(password);
        DdUser ddUser = new DdUser();
        ddUser.setId(id);
        ddUser.setPassword(encode);
        if(ddUserService.updateById(ddUser)){
           return Result.ok().message("Password changed successfully!!!");
        }
        return Result.error().message("Failed to modify password!!!");
    }

    @ApiOperation("delete user")
    @DeleteMapping("/{id}")
    public Result deleteById(@PathVariable Integer id){
        if(ddUserService.removeById(id)){
            return Result.ok().message("Deleted successfully!!!");
        }
        return Result.error().message("Delete failed!!!");
    }

}
  1. service

@Autowired
private DdUserRoleMapper ddUserRoleMapper;
/**
 * Get all operators
 */
@Override
public List<DdUser> getAllUser(DdUser ddUser) {

    return ddUserMapper.getAllUser(ddUser);
}

/**
 * Update user owned roles
 * @param uid User id
 * @param rids Role id to be modified by the user
 */
@Override
@Transactional//When you write updates yourself, you must add transactions
public Result updateUserRole(Integer uid, Integer[] rids) {
    //Delete it all first, or you'll have to judge it again and again. It's too expensive
    ddUserRoleMapper.delete(new QueryWrapper<DdUserRole>().eq("uid", uid));
    //If the menu id to be updated is empty, return directly
    if(null == rids||rids.length == 0){
        return Result.ok().message("User role updated successfully");
    }
    //If the menu id is passed, it will be updated
    ddUserMapper.insertRecord(uid,rids);
    return Result.ok().message("Menu updated successfully");
}
  1. mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dd.security.mapper.DdUserMapper">

    <!--Result set encapsulation-->
    <resultMap type="com.dd.security.entity.DdUser" id="DdUserResult">
        <result property="id"    column="id"    />
        <result property="username"    column="username"    />
        <result property="password"    column="password"    />
    </resultMap>

    <resultMap id="DdUserRoleResult" type="com.dd.security.entity.DdUser" extends="DdUserResult">
        <collection property="roles" ofType="com.dd.security.entity.DdRole">
            <id column="rid" property="id" />
            <result column="rname" property="name" />
            <result column="rnameZh" property="nameZh" />
        </collection>
    </resultMap>

    <select id="selectByUsername" parameterType="java.lang.String" resultType="com.dd.security.entity.DdUser">
        select id,username,password from dd_user where username=#{username}
    </select>

    <select id="getAllUser" resultMap="DdUserRoleResult" parameterType="com.dd.security.entity.DdUser">
        select
        u.id as id,
            u.username as username,
            u.`password` as `password`,
            r.id as rid,
            r.`name` as rname,
            r.name_zh as rnameZh
        from
            dd_user as u
        inner join
            dd_user_role as ur
        on
            u.id = ur.uid
        inner join
            dd_role as r
        on
            ur.rid = r.id
        <where>
            <trim>
                <if test="id != null  and id != '' and id!=0">and u.id = #{id}</if>
                <if test="username != null  and username != ''">and u.username like concat('%', #{username}, '%')</if>
            </trim>
        </where>
    </select>
    <!--Update user roles-->
    <insert id="insertRecord">
        insert into dd_user_role(uid,rid) values
        <foreach collection="rids" item="rid" separator=",">
            (#{uid},#{rid})
        </foreach>
    </insert>
</mapper>

4. Main function test

  1. menu

4, Front end docking

Due to space constraints, I put it in this article

Keywords: Java Spring Spring Security security

Added by michaelpalmer7 on Thu, 16 Dec 2021 07:45:51 +0200