【Shiro】Shiro 的学习教程(三)之 SpringBoot 集成 Shiro

目录

1、环境准备

新建一个 SpringBoot 工程,引入依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- shiro -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.5.3</version>
</dependency>

resources/static 目录下新建 pages,再新建两个 html 文件

index.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <h1>首页</h1>
</body>
</html>

login.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
    <h1>登录页</h1>
</body>
</html>

添加路由控制器 RoutingController

java 复制代码
@Controller
public class RoutingController {

    @GetMapping("/index")
    public String index() {
        return "pages/index.html";
    }

    @GetMapping("/login")
    public String login() {
        return "pages/login.html";
    }

}

访问:http://localhost:8080/indexhttp://localhost:8080/login 正常访问 index.htmllogin.html 页面。

2、引入 Shiro

①:添加一个自定义 Realm:

java 复制代码
public class UserRealm extends AuthorizingRealm {
    
    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    
    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }

}

暂不实现这两个方法

②:添加 Shiro 配置类 ShiroConfiguration

java 复制代码
@Configuration
public class ShiroConfiguration {

    // 1.创建 shiroFilter,负责拦截所有请求
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //给filter设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        //配置系统受限资源
        //配置系统公共资源
        Map<String,String> map = new HashMap<>();
        // authc 请求这个资源需要认证和授权
        map.put("/index", "authc");
        //默认认证界面路径
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    //2.创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //给安全管理器设置
        defaultWebSecurityManager.setRealm(realm);
        return defaultWebSecurityManager;
    }

    //3.创建自定义realm
    @Bean
    public Realm getRealm(){
        return new UserRealm();
    }

}

看下面代码:

java 复制代码
// authc 请求这个资源需要认证和授权
map.put("/index", "authc");

表示:访问 /index 路径,需要认证、授权;否则,无法访问


③:路由控制器 RoutingController

java 复制代码
@Controller
public class RoutingController {

    @GetMapping("/index")
    public String index() {
        return "pages/index.html";
    }

    @GetMapping("/login")
    public String login() {
        return "pages/login.html";
    }
}

启动项目再次访问 http://localhost:8080/index,发现页面跳转到登录页面。

这就说明我们的项目中成功的引入了 Shiro

3、实现认证、退出

3.1、使用死数据实现

修改 login.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
    <h1>登录页</h1>

    <form action="/user/login" method="post">
        用户名:<input type="text" name="username" > <br/>
        密码  : <input type="text" name="password"> <br>
        <input type="submit" value="登录">
    </form>

</body>
</html>

index.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <h1>首页</h1>
    <a href="/user/logout">退出用户</a>
</body>
</html>

UserController

java 复制代码
@Controller
@RequestMapping("/user")
public class UserController {

    @PostMapping("/login")
    public String login(String username, String password) {
        try {
            // 执行登录操作
            Subject subject = SecurityUtils.getSubject();
            // 认证通过后直接跳转到 index.html
            subject.login(new UsernamePasswordToken(username,password));
            return "redirect:/index";
        } catch (AuthenticationException e) {
            e.printStackTrace();
            // 如果认证失败仍然回到登录页面
            return "redirect:/login";
        }
    }

    @GetMapping("/logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        // 退出后仍然会到登录页面
        return "redirect:/login";
    }

}

UserRealm:实现认证

java 复制代码
public class UserRealm extends AuthorizingRealm {

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String principal = (String)authenticationToken.getPrincipal();
        // TODO 模拟数据库返回的数据
        if("christy".equals(principal)){
            return new SimpleAuthenticationInfo(principal,"123456", this.getName());
        }
        return null;
    }

}

输入的用户名是 christy,密码 123456,就可以认证通过进入到主页

3.2、引入数据库,添加注册功能

pom.xml 引入依赖:

xml 复制代码
<!-- mybatis plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>
<!-- Druid数据源 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>
<!-- Mysql -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

yml 添加配置:

yml 复制代码
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/sb_shiro?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC
      username: root
      password: root
      # 监控统计拦截的filters
      filters: stat,wall,slf4j,config
      # 配置初始化大小/最小/最大
      initial-size: 5
      min-idle: 5
      max-active: 20
      # 获取连接等待超时时间
      max-wait: 60000
      # 间隔多久进行一次检测,检测需要关闭的空闲连接
      time-between-eviction-runs-millis: 60000
      # 一个连接在池中最小生存的时间
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 'x'
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      # 打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
      pool-prepared-statements: false
      max-pool-prepared-statement-per-connection-size: 20

mybatis-plus:
  type-aliases-package: com.zzc.entity
  configuration:
    map-underscore-to-camel-case: true

sql 文件:

sql 复制代码
CREATE TABLE `t_user`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `password` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `salt` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `age` int NULL DEFAULT NULL,
  `email` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `address` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
后端代码

UserController:添加一个注册接口

java 复制代码
@Autowired
private UserService userService;

@PostMapping("/register")
public String register(User user) {
    try {
        userService.register(user);
        return "redirect:/login";
    } catch (Exception e) {
        return "redirect:/register";
    }
}

UserServiceImpl:未写接口

java 复制代码
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Override
    public void register(User user) {
        // 生成随机盐
        String salt = SaltUtil.getSalt(8);
        // 保存随机盐
        user.setSalt(salt);
        // 生成密码
        Md5Hash password = new Md5Hash(user.getPassword(), salt, 1024);
        // 保存密码
        user.setPassword(password.toHex());
        save(user);
    }

}

UserMapper

java 复制代码
public interface UserMapper extends BaseMapper<User> {
}

User

java 复制代码
@Data
@TableName("t_user")
public class User {

    @TableId(type = IdType.AUTO)
    private Integer id;

    // 用户名
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String username;

    // 密码
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String password;

    // 盐
    @TableField(fill = FieldFill.INSERT)
    private String salt;

    // 年龄
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer age;

    // 邮箱
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String email;

    // 地址
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String address;
}

RoutingController:路由

java 复制代码
@GetMapping("/register")
public String register() {
    return "pages/register.html";
}

ShiroConfiguration:过滤掉部分接口

java 复制代码
Map<String,String> map = new HashMap<>();
// authc 请求这个资源需要认证和授权
map.put("/login", "anon");
map.put("/register", "anon");
map.put("/user/register", "anon");
map.put("/user/login", "anon");
map.put("/index", "authc");
前端代码

添加一个注册页面 register.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册页面</title>
</head>
<body>
<form action="/user/register" method="post">
    用户名:<input type="text" name="username" > <br/>
    密码  : <input type="text" name="password"> <br>
    <input type="submit" value="立即注册">
</form>
</body>
</html>

启动项目,在注册页面进行注册

可以看到我们注册的用户已经顺利保存到数据库,而且密码是经过加密的

3.3、MD5、Salt 的认证流程

上面我们完成了基于 MD5+Salt 的注册流程,保存到数据库的密码都是经过加密处理的,这时候再用最初的简单密码匹配器进行 equals() 方法进行登录显然是不行的了,我们下面来改造一下认证的流程

UserRealm:修改认证(从数据库中获取)

java 复制代码
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    String principal = (String)authenticationToken.getPrincipal();
    // 由于 UserRealm 并没有交由工厂管理,故不能注入 UserService
    UserService userService = ApplicationContextUtil.getBean(UserService.class);
    // 数据库返回的数据
    User user = userService.findByUsername(principal);
    if (Objects.nonNull(user)) {
        return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), new CustomerByteSource(user.getSalt()), this.getName());
    }
    return null;
}

ShiroConfiguration:修改 Reaml 配置(添加密码配置器)

java 复制代码
@Bean
public Realm getRealm(){
    UserRealm userRealm = new UserRealm();
    // 设置密码匹配器
    HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
    // 设置加密方式
    credentialsMatcher.setHashAlgorithmName("MD5");
    // 设置散列次数
    credentialsMatcher.setHashIterations(1024);
    userRealm.setCredentialsMatcher(credentialsMatcher);
    return userRealm;
}

ApplicationContextUtil:获取 Spring 的 bean

java 复制代码
@Component
public class ApplicationContextUtil implements ApplicationContextAware {

    public static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    public static <T> T getBean(Class<T> clazz){
        return context.getBean(clazz);
    }

}

CustomerByteSource

java 复制代码
public class CustomerByteSource implements ByteSource, Serializable {
    private byte[] bytes;
    private String cachedHex;
    private String cachedBase64;

    public CustomerByteSource() {

    }

    public CustomerByteSource(byte[] bytes) {
        this.bytes = bytes;
    }

    public CustomerByteSource(char[] chars) {
        this.bytes = CodecSupport.toBytes(chars);
    }

    public CustomerByteSource(String string) {
        this.bytes = CodecSupport.toBytes(string);
    }

    public CustomerByteSource(ByteSource source) {
        this.bytes = source.getBytes();
    }

    public CustomerByteSource(File file) {
        this.bytes = (new CustomerByteSource.BytesHelper()).getBytes(file);
    }

    public CustomerByteSource(InputStream stream) {
        this.bytes = (new CustomerByteSource.BytesHelper()).getBytes(stream);
    }

    public static boolean isCompatible(Object o) {
        return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
    }

    @Override
    public byte[] getBytes() {
        return this.bytes;
    }

    @Override
    public boolean isEmpty() {
        return this.bytes == null || this.bytes.length == 0;
    }

    @Override
    public String toHex() {
        if (this.cachedHex == null) {
            this.cachedHex = Hex.encodeToString(this.getBytes());
        }

        return this.cachedHex;
    }

    @Override
    public String toBase64() {
        if (this.cachedBase64 == null) {
            this.cachedBase64 = Base64.encodeToString(this.getBytes());
        }

        return this.cachedBase64;
    }

    @Override
    public String toString() {
        return this.toBase64();
    }

    @Override
    public int hashCode() {
        return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (o instanceof ByteSource) {
            ByteSource bs = (ByteSource) o;
            return Arrays.equals(this.getBytes(), bs.getBytes());
        } else {
            return false;
        }
    }

    private static final class BytesHelper extends CodecSupport {
        private BytesHelper() {
        }

        public byte[] getBytes(File file) {
            return this.toBytes(file);
        }

        public byte[] getBytes(InputStream stream) {
            return this.toBytes(stream);
        }
    }

}

重启项目进行测试

4.、实现授权

4.1、基于角色授权

sql 文件:

sql 复制代码
CREATE TABLE `t_role`  (
  `id` int NOT NULL,
  `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
INSERT INTO `t_role` VALUES (1, 'admin');
INSERT INTO `t_role` VALUES (2, 'user');

CREATE TABLE `t_user_role_ref`  (
  `id` int NOT NULL,
  `user_id` int NULL DEFAULT NULL,
  `role_id` int NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
INSERT INTO `t_user_role_ref` VALUES (1, 1, 1);
INSERT INTO `t_user_role_ref` VALUES (2, 2, 2);

Role

java 复制代码
@Data
@TableName("t_role")
public class Role {

    @TableId(type = IdType.AUTO)
    private Integer id;

    // 角色名
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String name;
}

UserRoleRef

java 复制代码
@Data
@TableName("t_user_role_ref")
public class UserRoleRef {

    @TableId(type = IdType.AUTO)
    private Integer id;

    // 用户Id
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer userId;

    // 角色id
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer roleId;
}

UserMapper

java 复制代码
public interface UserMapper extends BaseMapper<User> {
}

UserRoleRefMapper

java 复制代码
public interface UserRoleRefMapper extends BaseMapper<UserRoleRef> {
}

RoleServiceImpl

java 复制代码
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {

    @Autowired
    private UserRoleRefMapper userRoleRefMapper;

    @Override
    public List<Role> listByUserId(Integer userId) {
        QueryWrapper<UserRoleRef> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", userId);
        List<UserRoleRef> userRoleRefs = userRoleRefMapper.selectList(wrapper);
        List<Role> roles = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(userRoleRefs)) {
            List<Integer> roleIds = userRoleRefs.stream().map(UserRoleRef::getRoleId).distinct().collect(Collectors.toList());
            QueryWrapper<Role> queryWrapper = new QueryWrapper<>();
            queryWrapper.in("id", roleIds);
            roles = baseMapper.selectList(queryWrapper);
        }
        return roles;
    }

}

UserRealm:实现授权

java 复制代码
public class UserRealm extends AuthorizingRealm {
    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        UserService userService = ApplicationContextUtil.getBean(UserService.class);
        RoleService roleService = ApplicationContextUtil.getBean(RoleService.class);
        String principal = (String) principalCollection.getPrimaryPrincipal();
        User user = userService.findByUsername(principal);
        if (Objects.nonNull(user)) {
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            List<Role> roles = roleService.listByUserId(user.getId());
            if (CollectionUtils.isNotEmpty(roles)) {
                roles.forEach(role -> simpleAuthorizationInfo.addRole(role.getName()));
            }
            return simpleAuthorizationInfo;
        }
        return null;
    }
}

UserController:当前登录用户是什么角色就打印什么角色

java 复制代码
@PostMapping("/login")
public String login(String username, String password) {
    try {
        // 执行登录操作
        Subject subject = SecurityUtils.getSubject();
        // 认证通过后直接跳转到 index.html
        subject.login(new UsernamePasswordToken(username,password));
        // 授权
        if (subject.hasRole("admin")) {
            System.out.println("admin");
        } else if (subject.hasRole("user")) {
            System.out.println("user");
        }
        return "redirect:/index";
    } catch (AuthenticationException e) {
        e.printStackTrace();
        // 如果认证失败仍然回到登录页面
        return "redirect:/login";
    }
}

4.2、基于资源授权

sql 文件:

sql 复制代码
CREATE TABLE `t_permission`  (
  `id` int NOT NULL,
  `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `url` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
INSERT INTO `t_permission` VALUES (1, 'user:*:*', NULL);
INSERT INTO `t_permission` VALUES (2, 'order:*:*', NULL);
INSERT INTO `t_permission` VALUES (3, 'order:query:*', NULL);

CREATE TABLE `t_role_permission_ref`  (
  `id` int NOT NULL,
  `role_id` int NULL DEFAULT NULL,
  `permission_id` int NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
INSERT INTO `t_role_permission_ref` VALUES (1, 1, 1);
INSERT INTO `t_role_permission_ref` VALUES (2, 1, 2);
INSERT INTO `t_role_permission_ref` VALUES (3, 2, 3);

Permission

java 复制代码
@Data
@TableName("t_permission")
public class Permission {

    @TableId(type = IdType.AUTO)
    private Integer id;
    private String name;
    private String url;

}

RolePermissionRef

java 复制代码
@Data
@TableName("t_role_permission_ref")
public class RolePermissionRef {

    @TableId(type = IdType.AUTO)
    private Integer id;
    private Integer roleId;
    private Integer permissionId;

}

PermissionMapper

java 复制代码
public interface PermissionMapper extends BaseMapper<Permission> {
}

RolePermissionRefMapper

java 复制代码
public interface RolePermissionRefMapper extends BaseMapper<RolePermissionRef> {
}

PermissionServiceImpl

java 复制代码
@Service
public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permission> implements PermissionService {

    @Autowired
    private RolePermissionRefMapper rolePermissionRefMapper;

    @Override
    public List<Permission> listByRoleIds(List<Integer> roleIds) {
        QueryWrapper<RolePermissionRef> wrapper = new QueryWrapper<>();
        wrapper.in("role_id", roleIds);
        List<RolePermissionRef> rolePermissionRefs = rolePermissionRefMapper.selectList(wrapper);
        List<Permission> permissions = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(rolePermissionRefs)) {
            List<Integer> permissionIds = rolePermissionRefs.stream().map(RolePermissionRef::getPermissionId).distinct().collect(Collectors.toList());
            QueryWrapper<Permission> queryWrapper = new QueryWrapper<>();
            queryWrapper.in("id", permissionIds);
            permissions = baseMapper.selectList(queryWrapper);
        }
        return permissions;
    }

}

UserRealm

java 复制代码
public class UserRealm extends AuthorizingRealm {
    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        UserService userService = ApplicationContextUtil.getBean(UserService.class);
        RoleService roleService = ApplicationContextUtil.getBean(RoleService.class);
        PermissionService permissionService = ApplicationContextUtil.getBean(PermissionService.class);
        String principal = (String) principalCollection.getPrimaryPrincipal();
        User user = userService.findByUsername(principal);
        if (Objects.nonNull(user)) {
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            // 角色
            List<Role> roles = roleService.listByUserId(user.getId());
            if (CollectionUtils.isNotEmpty(roles)) {
                roles.forEach(role -> simpleAuthorizationInfo.addRole(role.getName()));
            }
            // 资源
            List<Integer> roleIds = roles.stream().map(Role::getId).distinct().collect(Collectors.toList());
            List<Permission> permissions = permissionService.listByRoleIds(roleIds);
            if (CollectionUtils.isNotEmpty(permissions)) {
                permissions.forEach(permission -> simpleAuthorizationInfo.addStringPermission(permission.getName()));
            }
            return simpleAuthorizationInfo;
        }
        return null;
    }
}

UserController

java 复制代码
@PostMapping("/login")
public String login(String username, String password) {
    try {
        // 执行登录操作
        Subject subject = SecurityUtils.getSubject();
        // 认证通过后直接跳转到 index.html
        subject.login(new UsernamePasswordToken(username,password));
        // 角色-授权
        if (subject.hasRole("admin")) {
            System.out.println("admin");
        } else if (subject.hasRole("user")) {
            System.out.println("user");
        }
        // 资源-授权
        if (subject.isPermitted("order:*:*")) {
            System.out.println("order:*:*");
        } else if (subject.isPermitted("user:*:*")) {
            System.out.println("user:*:*");
        }
        return "redirect:/index";
    } catch (AuthenticationException e) {
        e.printStackTrace();
        // 如果认证失败仍然回到登录页面
        return "redirect:/login";
    }
}

重启项目测试

5、引入缓存

5.1、EhCache 实现缓存

Shiro 提供了缓存管理器,这样在用户第一次认证授权后访问其受限资源的时候就不用每次查询数据库从而达到减轻数据压力的作用,使用 Shiro 的缓存管理器也很简单

xml 复制代码
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.5.3</version>
</dependency>

ShiroConfiguration

java 复制代码
@Bean
public Realm getRealm(){
    UserRealm userRealm = new UserRealm();
    // 设置密码匹配器
    HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
    // 设置加密方式
    credentialsMatcher.setHashAlgorithmName("MD5");
    // 设置散列次数
    credentialsMatcher.setHashIterations(1024);
    userRealm.setCredentialsMatcher(credentialsMatcher);

    // 设置缓存管理器
    userRealm.setCacheManager(new EhCacheManager());
    // 开启全局缓存
    userRealm.setCachingEnabled(true);
    // 开启认证缓存并指定缓存名称
    userRealm.setAuthenticationCachingEnabled(true);
    userRealm.setAuthenticationCacheName("authenticationCache");
    // 开启授权缓存并指定缓存名称
    userRealm.setAuthorizationCachingEnabled(true);
    userRealm.setAuthorizationCacheName("authorizationCache");
    return userRealm;
}

这样就将 EhCache 集成进来了,但是 shiro 的这个缓存是本地缓存,也就是说当程序宕机重启后仍然需要从数据库加载数据,不能实现分布式缓存的功能

5.2、集成 Redis 实现 Shiro 缓存

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

RedisCache

java 复制代码
public class RedisCache<K,V> implements Cache<K,V> {

    private String cacheName;

    public RedisCache() {
    }

    public RedisCache(String cacheName) {
        this.cacheName = cacheName;
    }

    @Override
    public V get(K k) throws CacheException {
        System.out.println("获取缓存:"+ k);
        return (V) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
    }

    @Override
    public V put(K k, V v) throws CacheException {
        System.out.println("设置缓存key: "+k+" value:"+v);
        getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);
        return null;
    }

    @Override
    public V remove(K k) throws CacheException {
        return (V) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
    }

    @Override
    public void clear() throws CacheException {
        getRedisTemplate().delete(this.cacheName);
    }

    @Override
    public int size() {
        return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
    }

    @Override
    public Set<K> keys() {
        return getRedisTemplate().opsForHash().keys(this.cacheName);
    }

    @Override
    public Collection<V> values() {
        return getRedisTemplate().opsForHash().values(this.cacheName);
    }

    private RedisTemplate getRedisTemplate(){
        RedisTemplate redisTemplate = (RedisTemplate)ApplicationContextUtil.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

RedisCacheManager

java 复制代码
public class RedisCacheManager implements CacheManager {
    @Override
    public <K, V> Cache<K, V> getCache(String s) throws CacheException {
        System.out.println("缓存名称: "+ s);
        return new RedisCache<K,V>(s);
    }
}

ShiroConfiguration

java 复制代码
// 设置缓存管理器
//userRealm.setCacheManager(new EhCacheManager());
userRealm.setCacheManager(new RedisCacheManager());

重启测试项目

一篇适合小白的Shiro教程

相关推荐
StickToForever40 分钟前
第4章 信息系统架构(五)
经验分享·笔记·学习·职场和发展
闲猫2 小时前
go orm GORM
开发语言·后端·golang
丁卯4043 小时前
Go语言中使用viper绑定结构体和yaml文件信息时,标签的使用
服务器·后端·golang
Tirzano3 小时前
springsecurity自定义认证
spring boot·spring
leegong231114 小时前
学习PostgreSQL专家认证
数据库·学习·postgresql
Moonnnn.5 小时前
51单片机学习——动态数码管显示
笔记·嵌入式硬件·学习·51单片机
南宫生5 小时前
力扣每日一题【算法学习day.132】
java·学习·算法·leetcode
技术小齐6 小时前
网络运维学习笔记 016网工初级(HCIA-Datacom与CCNA-EI)PPP点对点协议和PPPoE以太网上的点对点协议(此处只讲华为)
运维·网络·学习
竹言笙熙6 小时前
代码审计初探
学习·web安全
日记成书6 小时前
物联网智能项目
物联网·学习