shiro

一、shiro是什么?

​ Shiro是Apache下的一个开源项目。shiro属于轻量级框架,相对于SpringSecurity简单的多,也没有SpringSecurity那么复杂 。

二、主要功能

shiro主要有三大功能模块:

1. Subject:主体,一般指用户。
2. SecurityManager:安全管理器,管理所有Subject,可以配合内部安全组件。(类似于SpringMVC中的DispatcherServlet)
3. Realms:用于进行权限信息的验证,一般需要自己实现。

细分功能

1. Authentication:身份认证/登录(账号密码验证)。
2. Authorization:授权,即角色或者权限验证。
3. Session Manager:会话管理,用户登录后的session相关管理。
4. Cryptography:加密,密码加密等。
5. Web Support:Web支持,集成Web环境。
6. Caching:缓存,用户信息、角色、权限等缓存到如redis等缓存中。
7. Concurrency:多线程并发验证,在一个线程中开启另一个线程,可以把权限自动传播过去。
8. Testing:测试支持;
9. Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。
10. Remember Me:记住我,登录后,下次再来的话不用登录了

三、具体实现

1、项目目录

1604471130107.png

pom.xml依赖文件

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>shiro_demo</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/>
</parent>

<properties>
<java.version>1.8</java.version>
<spring.shiro.version>1.6.0</spring.shiro.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${spring.shiro.version}</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--页面模板依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--热部署依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

Permissions权限实体类

1
2
3
4
5
6
7
8
9
10
11
package org.example.bean;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Permissions {
private String id;
private String permissionsName;
}

Role角色实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example.bean;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.Set;

@Data
@AllArgsConstructor
public class Role {

private String id;
private String roleName;
/**
* 角色对应权限集合
*/
private Set<Permissions> permissions;
}

User实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.example.bean;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.Set;

@Data
@AllArgsConstructor
public class User {

private String id;
private String userName;
private String password;

private Set<Role> roles;
}

业务层,模拟数据库

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
package org.example.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.example.bean.Permissions;
import org.example.bean.Role;
import org.example.bean.User;
import org.example.service.LoginService;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

@Service
@Slf4j
public class LoginServiceImpl implements LoginService {
@Override
public User getUserByName(String getMapByName) {
return getMapByName(getMapByName);
}

/**
* 模拟数据库查询
*
* @param userName 用户名
* @return User
*/
private User getMapByName(String userName) {
Permissions permissions1 = new Permissions("1", "query");
Permissions permissions2 = new Permissions("2", "add");
Set<Permissions> permissionsSet = new HashSet<>(2);
permissionsSet.add(permissions1);
permissionsSet.add(permissions2);
Role role = new Role("1", "admin", permissionsSet);
Set<Role> roleSet = new HashSet<>();
roleSet.add(role);
User user = new User("1", "wsl", "123456", roleSet);
Map<String, User> map = new HashMap<>(1);
map.put(user.getUserName(), user);

Set<Permissions> permissionsSet1 = new HashSet<>(2);
permissionsSet1.add(permissions1);
Role role1 = new Role("2", "user", permissionsSet1);
Set<Role> roleSet1 = new HashSet<>();
roleSet1.add(role1);
User user1 = new User("2", "zhangsan", "123456", roleSet1);
map.put(user1.getUserName(), user1);
return map.get(userName);
}
}

CustomRealm用户权限配置类

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
package org.example.shiro;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.example.bean.User;
import org.example.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

/**
* 权限控制以及权限认证
*
*/
public class CustomRealm extends AuthorizingRealm {
@Autowired
private LoginService loginService;

/**
* 权限配置类
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取当前登录用户名
String userName = principalCollection.getPrimaryPrincipal().toString();
User user = loginService.getUserByName(userName);

//添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
user.getRoles().forEach(role -> {
simpleAuthorizationInfo.addRole(role.getRoleName());
role.getPermissions().forEach(permission -> simpleAuthorizationInfo.addStringPermission(permission.getPermissionsName()));
});
return simpleAuthorizationInfo;

}

/**
* 权限认证类
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
Object username = authenticationToken.getPrincipal();
if (StringUtils.isEmpty(username)) {
return null;
}
//获取用户信息
String name = username.toString();
User user = loginService.getUserByName(name);
if (user == null) {
//这里返回后会报出对应异常
return null;
} else {
//这里验证authenticationToken和simpleAuthenticationInfo的信息
return new SimpleAuthenticationInfo(name, user.getPassword(), getName());
}
}
}

shiro配置类

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
72
73
74
75
76
77
78
package org.example.config;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.example.shiro.CustomRealm;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
* shiro具体配置类
*/
@Configuration
public class ShiroConfig {


@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}

/**
* 将自己的验证方式加入容器
*/
@Bean
public CustomRealm myShiroRealm() {
return new CustomRealm();
}

/**
* 权限管理,配置主要是Realm的管理认证
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}

/**
* Filter工厂,设置对应的过滤条件和跳转条件
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> map = new HashMap<>();
//登出
map.put("/logout", "logout");
//对所有用户认证
map.put("/**", "authc");
//登录
shiroFilterFactoryBean.setLoginUrl("/login");
//首页
shiroFilterFactoryBean.setSuccessUrl("/index");
//错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}


@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}

ExceptionHandler统一异常处理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.example.handler;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
@Slf4j
public class MyExceptionHandler {

@ExceptionHandler
@ResponseBody
public String ErrorHandler(AuthorizationException e) {
log.error("没有通过权限验证!", e);
return "没有通过权限验证!";
}
}

controller

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
package org.example.controller;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.example.bean.User;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class UserController {

@GetMapping("/login")
public String login(User user) {
if (StringUtils.isEmpty(user.getUserName()) || StringUtils.isEmpty(user.getPassword())) {
return "请输入用户名和密码!";
}
//用户认证信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
user.getUserName(),
user.getPassword()
);
try {
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(usernamePasswordToken);
} catch (UnknownAccountException e) {
log.error("用户名不存在!", e);
return "用户名不存在!";
} catch (AuthenticationException e) {
log.error("账号或密码错误!", e);
return "账号或密码错误!";
} catch (AuthorizationException e) {
log.error("没有权限!", e);
return "没有权限";
}
return "login success";
}

@RequiresRoles("admin")
@GetMapping("/admin")
public String admin() {
return "admin success!";
}

@RequiresPermissions("query")
@GetMapping("/index")
public String index() {
return "index success!";
}

@RequiresPermissions("add")
@GetMapping("/add")
public String add() {
return "add success!";
}
}