shiro2

Shiro 五表整合

1.环境准备

  • 导入springboot_shiro项目,并执行menu.sql

  • 导入ShiroConfig

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    package com.cjc.config;

    import com.cjc.realm.LoginRealm;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.filter.mgt.DefaultFilter;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    import java.util.LinkedHashMap;

    /**
    */
    @Configuration
    public class ShiroConfig {

    @Bean
    public LoginRealm loginRealm(){
    LoginRealm loginRealm = new LoginRealm();
    loginRealm.setCredentialsMatcher(hashedCredentialsMatcher());
    return loginRealm;
    }

    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(){
    DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
    //关联loginRealm
    defaultWebSecurityManager.setRealm(loginRealm());
    return defaultWebSecurityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(){
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    //设置安全管理器
    shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager());
    //设置拦截路径
    LinkedHashMap<String, String> map = new LinkedHashMap<>();
    map.put("/loginController/**", DefaultFilter.anon.name());
    //放开js静态资源
    map.put("/js/**", DefaultFilter.anon.name());
    //退出登录
    map.put("/logout", DefaultFilter.logout.name());
    //其余路径 统统拦截
    map.put("/**", DefaultFilter.authc.name());

    //放入过滤器中
    shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
    //设置登录页面
    shiroFilterFactoryBean.setLoginUrl("/loginController/toLogin");
    //设置没有权限页面
    shiroFilterFactoryBean.setUnauthorizedUrl("/loginController/unauthorizedurl");
    return shiroFilterFactoryBean;
    }


    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
    HashedCredentialsMatcher hash = new HashedCredentialsMatcher();
    //指定加密方式
    hash.setHashAlgorithmName("md5");
    //指定加密次数
    hash.setHashIterations(7);
    //指定编码
    hash.setStoredCredentialsHexEncoded(true);

    return hash;
    }
    }

2.开发五表的登录认证

  • 创建LoginRealm类
1
2
3
4
5
6
7
8
9
10
11
12
13
public class LoginRealm extends AuthorizingRealm {
@Autowired
private PersonServiceI personServiceI;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}
  • 修改ShiroConfig类,更换成自己的LoginRealm类并且设置拦截路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@Configuration
public class ShiroConfig {

@Bean
public LoginRealm loginRealm(){
LoginRealm loginRealm = new LoginRealm();
loginRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return loginRealm;
}

@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//关联loginRealm
defaultWebSecurityManager.setRealm(loginRealm());
return defaultWebSecurityManager;
}

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager());
//设置拦截路径
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put("/loginController/**", DefaultFilter.anon.name());
//放开js静态资源
map.put("/js/**", DefaultFilter.anon.name());
//退出登录
map.put("/logout", DefaultFilter.logout.name());
//其余路径 统统拦截
map.put("/**", DefaultFilter.authc.name());

//放入过滤器中
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
//设置登录页面
shiroFilterFactoryBean.setLoginUrl("/loginController/toLogin");
//设置没有权限页面
shiroFilterFactoryBean.setUnauthorizedUrl("/loginController/unauthorizedurl");
return shiroFilterFactoryBean;
}


@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hash = new HashedCredentialsMatcher();
//指定加密方式
hash.setHashAlgorithmName("md5");
//指定加密次数
hash.setHashIterations(5);
//指定编码
hash.setStoredCredentialsHexEncoded(true);

return hash;
}
}

  • 修改原始登录逻辑,换成shiro控制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RequestMapping("login")
@ResponseBody
public ResponseResult login(HttpServletRequest request, String pname, String pwd, Model model) throws Exception {
//判断用户名和密码是否为空
if(StrUtil.isEmpty(pname)||StrUtil.isEmpty(pwd)){
return new ResponseResult(ResultConstant.USERNAME_PASSWORD_NULL_ERROR_CODE, ResultConstant.USERNAME_PASSWORD_NULL_ERROR_DATA);
}
//创建subject对象
Subject subject = SecurityUtils.getSubject();
//封装数据
UsernamePasswordToken tok = new UsernamePasswordToken(pname, pwd);
try {
//校验用户名
subject.login(tok);
return new ResponseResult(ResultConstant.LOGIN_SUCCESS_CODE, ResultConstant.LOGIN_SUCCESS_DATA);
} catch (UnknownAccountException e) {
e.printStackTrace();
return new ResponseResult(ResultConstant.USERNAME_PASSWORD_ERROR_CODE, ResultConstant.USERNAME_PASSWORD_ERROR_DATA);
}catch (IncorrectCredentialsException e){
return new ResponseResult(ResultConstant.USERNAME_PASSWORD_ERROR_CODE, ResultConstant.USERNAME_PASSWORD_NULL_ERROR_DATA);
}
}
  • 在LoginRealm中增加登录判断代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class LoginRealm extends AuthorizingRealm {
@Autowired
private PersonServiceI personServiceI;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//强转参数
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
//从数据库中获取对象
TPerson person = personServiceI.findPersonByName(token.getUsername());
//判断
if(person==null){
return null;
}
//校验密码
SimpleAuthenticationInfo loginRealm = new SimpleAuthenticationInfo(person, person.getPwd(), ByteSource.Util.bytes(person.getSalt()), "loginRealm");
return loginRealm;
}
}

  • 修改获取树形菜单代码
1
2
3
4
5
6
7
8
9
10
11
//查询treeView菜单树
@RequestMapping("/getMenu")
@ResponseBody
public List<Map<String,Object>> getMenu(HttpServletRequest request){
//根据用户id查询菜单树
// HttpSession session = request.getSession();
// TPerson person = (TPerson) session.getAttribute("user");
TPerson tPerson= (TPerson)SecurityUtils.getSubject().getPrincipal();
List<Map<String,Object>> menus = menuservice.getMenu(tPerson.getPid());
return menus;
}

3.开发五表的权限认证

通过上面测试发现,只要是登录成功的用户,在改变访问地址后,依旧可以访问相关路径的资源,因此需要给各个资源加入权限认证。

1681625969247

  • 在ShiroConfig中加入权限路径
1
2
3
 //配置用户管理,角色管理拦截路径
map.put("/personController/list", "perms[person-list]");
map.put("/roleController/list", "perms[role-list]");
  • 测试

1681626878494

  • 配置用户管理,角色管理权限
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Autowired
private PersonServiceI personServiceI;
@Autowired
private MenuServiceI menuServiceI;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取用户信息
TPerson tPerson = (TPerson)SecurityUtils.getSubject().getPrincipal();
//根据当前用户id获取menu权限列表
List<String> menuByPersonPerms = menuServiceI.findMenuByPersonPerms(tPerson.getPid());
//创建对象
SimpleAuthorizationInfo sim = new SimpleAuthorizationInfo();
sim.addStringPermissions(menuByPersonPerms);
return sim;
}

注意:

findMenuByPersonPerms方法可以通过修改mapper.xml文件获取权限字符串列表

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 根据用户id,查询用户所拥有的perms权限列表 -->
<select id="findMenuByPersonPerms" resultType="string">
SELECT DISTINCT m.perms FROM menu m
LEFT JOIN role_menu rm ON m.id = rm.menu_id
LEFT JOIN t_role r ON rm.role_id = r.role_id
LEFT JOIN person_role pr ON r.role_id = pr.role_id
LEFT JOIN t_person p ON pr.person_id = p.pid
<where>
<if test=" pid != null ">
and p.pid = #{pid}
</if>
</where>
</select>
  • 测试

1681627827442

4.按钮权限控制

4.1 设置按钮无权限访问

普通用户登录成功之后是可以使用角色管理的添加功能的,这个时候如果我们不想让普通用户使用添加功能,那么就应该把权限控制到按钮。

1681632581376

  • 使用管理员操作【资源管理】,增加【角色增加】的功能

1681632735341

  • 使用管理员的角色管理为自己增加【角色增加】功能

1681631068768

  • 在ShiroConfig中增加【角色增加】代码
1
2
//增加角色配置
map.put("/roleController/toAddRole","perms[role_add]");

1681632434493


4.2 隐藏按钮选项

  • 导入java包
1
2
3
4
5
6
7
<!-- thymel对shiro的扩展坐标 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>

  • 添加ShiroDialect配置
1
2
3
4
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
  • d导入命名空间以及在按钮处添加shiro标签
1
2
3
4
5
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml" xmlns:shiro="http://www.w3.org/1999/xhtml">

<div id="toolbar" shiro:hasPermission="add-role">
<input type="button" value="添加" onclick="toAddRole()" class="btn btn-primary">
</div>
  • 测试

1681634342078

4.3 去除重复菜单

  • 使用admin登录系统后发现,针对于角色增加功能重复

1681634662281

  • 解决:修改查询菜单sql语句,加入menu表中button判断条件即可
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 根据用户id,查询用户所拥有的权限 -->
<select id="findMenuByPersonId" resultMap="BaseResultMap">
SELECT DISTINCT m.* FROM menu m
LEFT JOIN role_menu rm ON m.id = rm.menu_id
LEFT JOIN t_role r ON rm.role_id = r.role_id
LEFT JOIN person_role pr ON r.role_id = pr.role_id
LEFT JOIN t_person p ON pr.person_id = p.pid
<where>
<if test=" pid != null ">
and p.pid = #{pid} and m.button=0
</if>
</where>
</select>
  • 测试:

1681634844966