功能需求:通过角色权限配置,实现不同角色功能的添加删除

文章从底层开始解说,依次为数据库-pojo层-mapper层-service层-shiro配置-controller层-前端页面一点点

数据库设计:

思路梳理

每个用户对应一个角色(一对一);每种角色对应多个权限(一对多),所以角色与权限之间应建立一个中间表,管理角色对应的多个权限

表设计

user表(与角色表id之间一对一关系):

springboot Shiro接口权限 springboot数据权限设计_User

 role表:

                                       

springboot Shiro接口权限 springboot数据权限设计_mybatis_02

 

permission权限表(其中permissionLogo为最终shiro注解添加权限要注入的值,留个标记-插个眼): 

 

springboot Shiro接口权限 springboot数据权限设计_java_03

 rolepermission角色权限表(实现角色与权限之间一对多的关系):

springboot Shiro接口权限 springboot数据权限设计_java_04

pojo层:

对应数据库创建User,Role,Permission,RolePermission四个实体类

User类

@Datapublic class User {
    private Integer id;             //用户id
    private String userCode;        //用户账号
    private String userName;        //用户名称
    private String password;        //密码
    private Integer userRoleId;     //用户角色id
}

Role类

@Data/**
 * 角色表
 */
public class Role {
    private Integer id;         //角色id
    private String roleCode;    //角色编码
    private String roleName;    //角色名称
}

permission类


@Datapublic class Permission {
    private Integer id;     //权限id
    private String permissionName;      //权限名称
    private String url;     //权限url
    private String permissionLogo;      //权限标识
}


RolePermission类


@Datapublic class RolePermission {
    private Integer id;     //角色-权限表关联主键id
    private Integer roleId;     //角色id
    private Integer permissionId;       //权限id
}


mapper层

需要两个mapper:UserMapper、PermissionMapper分别实现数据库数据的增删改查

UserMapper

UserMapper主要实现根据用户名实现用户的查询(为什么没有密码呢?因为shiro层会帮我们做,下面你就知道了-再插个眼(写到下面回来补充一嘴,UserRealm类中的doGetAuthenticationInfo方法配合Controller层中的登录实现用户名密码的认证)),实现用户的登录和认证


@Mapperpublic interface UserMapper {
 
//    根据名称查询用户    @Select("select * from user where userName=#{userName}")
    User getUserByUserNameLogin(@Param("userName") String userName);
}


PermissionMapper

PermissionMapper实现四个功能,根据角色id查出所有的权限id,再根据权限id查询权限logo,这两个功能已经能实现用户权限的赋予,后面两个功能实现角色权限的添加与删除

@Mapperpublic interface PermissionMapper {
    //    根据角色id查出所有的权限id
    @Select("select permissionId from rolepermission where roleId=#{roleId}")
    List<Integer> getRolePermissionByRoleId(@Param("roleId") Integer roleId);
//    根据权限id查询权限logo
    @Select("select permissionLogo from permission where id=#{id}")
    String getPermissionLogoById(@Param("id") Integer id);
//权限的添加与修改,就是角色权限表里内容的添加与删除,因为使用checkbox进行添加删除,不用担心重复添加或删除的意外
//根据能否在角色权限表里查询到权限id对应的行,判断删除或添加权限
    @Select("select count(1) from rolepermission where roleId=#{roleId} and permissionId=#{permissionId}")
    int getPermissionCountByPermissionAndRoleId(@Param("roleId") Integer roleId,@Param("permissionId") Integer permissionId);
//    添加权限
    @Insert("insert into rolepermission(roleId,permissionId) values(#{roleId},#{permissionId})")
    int addPermission(@Param("roleId") Integer roleId,@Param("permissionId") Integer permissionId);
//    删除权限
    @Delete("delete from rolepermission where roleId=#{roleId} and permissionId=#{permissionId}")
    int deletePermission(@Param("roleId") Integer roleId,@Param("permissionId") Integer permissionId);
}

service层

UserMapper和PermissionMapper有其对应的service层:

UserService


public interface UserService {

 
//    根据名称查询用户    User getUserByUserNameLogin(String userName);
}


UserServiceImpl


@Servicepublic class UserServiceImpl implements UserService{

    @Resource
    private UserMapper userMapper;
 
/**     * 根据名称查询用户
     * @param userName
     * @return
     */
    @Override
    public User getUserByUserNameLogin(String userName) {
        return userMapper.getUserByUserNameLogin(userName);
    }
}


偷个懒,,,servce层相信大家都会,对!一定都会,都是苗子啊~

shiro配置

shiro需要一个配置类ShiroCofig以及一个自定义类UserRealm

ShiroConfig配置类


@Configurationpublic class ShiroConfig {
    //    3    ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//    设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        /**
         * anon:无需认证就可以访问
         * authc:必须认证了才能访问
         * user:必须拥有 记住我 功能才能用
         * perms:拥有对某个资源的权限才能访问
         * role:拥有某个角色权限才能访问
         */
        Map<String, String> filterMap = new LinkedHashMap<>();
//        注意顺序,先认证后授权
        // 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 :这是一个坑呢,一不小心代码就不好使了;
        // 授权使用注解在controller中实现
//        设置登录的请求
//        bean.setLoginUrl("/toLogin");

//我这里没做未授权的页面跳转的url,是因为setUnauthorizedUrl()没起作用,最后会给出解决方法

        bean.setFilterChainDefinitionMap(filterMap);

        return bean;
    }

    //    2     DefaultWebSecurityManager
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//        关联UserRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    //    1   创建realm对象,需自定义类
    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }

//    开启对shiro注解的支持
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(defaultWebSecurityManager);
        return advisor;
    }
    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        autoProxyCreator.setProxyTargetClass(true);
        return autoProxyCreator;
    }

    //整合ShiroDialect:用来整合shiro thymeleaf
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }
}


UserRealm类

UserRealm类中的doGetAuthenticationInfo方法配合controller层中的登录实现用户名密码的认证

//自定义的UserRealm extends AuthorizingRealmpublic class UserRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;
    @Autowired
    private PermissionService permissionService;

    //    授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了=>授权doGetAuthorizationInfo");

//        SimpleAuthorizationInfo
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//        拿到当前登录的这个对象
        Subject subject = SecurityUtils.getSubject();
        User currentUser = (User) subject.getPrincipal();  //拿到User对象
//        设置当前用户的权限
        Set<String> permissions = new HashSet<>();
        List<Integer> permissionIdList = permissionService.getRolePermissionByRoleId(currentUser.getUserRoleId());
        for (int i = 0; i < permissionIdList.size(); i++) {
            String permissionLogo = permissionService.getPermissionLogoById(permissionIdList.get(i));
                System.out.println("Logo----------" + permissionLogo);
                permissions.add(permissionLogo);
        }
        info.addStringPermissions(permissions);

        return info;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了=>授权==认证doGetAuthenticationInfo");

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
//        用户名,密码~ 连接数据库获取
        User user = userService.getUserByUserNameLogin(userToken.getUsername());

        if (user == null) {  //表示没有这个人
            return null;  //抛出异常  UnknownAccountException
        }

//        密码认证,shiro做
        return new SimpleAuthenticationInfo(user, user.getPassword(), "");
    }
}

Controller层

下面的login方法与UserRealm中的认证联合实现登录与认证,最后的两个方法add和update实现权限的授权,至此其实已经实现了权限进入页面了,再往下简单介绍一些权限的添加与删除


@Controllerpublic class LoginController {
    @Autowired
    private UserService userService;
    @RequestMapping({"/", "/toLogin"})
    public String toLogin(){
        return "login";
    }
    @RequestMapping("/dologin")
    public String login(String userName, String password, Model model){
//    获取当前用户
        Subject subject = SecurityUtils.getSubject();
//        封装用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
        try {
            subject.login(token);   //执行登录方法,若无异常说明OK了
            return "home";
        } catch (UnknownAccountException e) { // 用户名不存在
            model.addAttribute("msg","用户名错误");
            return "login";
        }catch (IncorrectCredentialsException e) { // 密码不存在
            model.addAttribute("msg","密码错误");
            return "login";
        }
    }
    @RequiresPermissions("add")
    @RequestMapping("/add")
    public String add(){
        return "test";
    }
    @RequiresPermissions("update")
    @RequestMapping("/update")
    public String update(){
        return "test1";
    }
}


前端HTML


<html lang="zz" xmlns:th="http://www.thymeleaf.org"      xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"
>
 
....
 
<li shiro:hasPermission="workstation:one">    <a class="J_menuItem" th:href="@{/add}" data-index="0">个人资料修改</a>
</li>


添加-删除权限

简单提一下,因为权限的配置和数据库表已经弄好了,权限的添加和删除就是往数据库rolepermission表添加数据即可(我的permission权限表是固定的)

setUnauthorizedUrl()未授权页面不起作用解决

网上有很多解决方法,大家也可以点进去setUnauthorizedUrl()方法自己研究下,会发现....,我也没看懂,不过解决办法倒是有了,自己定义一个全局异常捕获类就行


@ControllerAdvice@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {
    @ExceptionHandler(value = AuthorizationException.class)
    public String handleAuthorizationException() {
 
//        点击未授权的链接,进入的未授权提示页        return "/error/noauth";
    }
}

不过其实前端只要加上shiro:hasPermission="workstation:one"该语句后,未授权的页面链接根本不会显示,大家看自己的需求