Shiro是历史悠久的权限管理框,简单易用,易用集成,同时权限管理也是每个项目必不可少的功能。Spring Boot是Java领域炙手可热的脚手架框架。今天这篇文章就带大家将这两个框架进行整合。

通常Spring Boot中整合Shiro,有两种方案:第一,基于原生API进行整合;第二,基于Shiro官方Starter整合。

整体而言,官方Starter整合并没有方便很多,因此,本文主要以原则API进行整合,

下面就来看看具体的整合方式。

创建Spring Boot项目

创建Spring Boot项目通常有两种方式,一种是通过官网创建之后导入到IDE中。一种是通过Idea集成的Spring Boot进行创建。

这里以Idea创建为例,在IDEA中通过Spring Initializr创建项目,在引入依赖时引入Web依赖即可:

springboot-shiro

一路Next,项目创建完成。

引入Shiro依赖

项目创建完成之后,在pom.xml中加入Shiro相关的依赖。关于Shiro的版本信息大家需要留意,尽量使用1.6.0及以上版本。小于1.6.0的版本存在”绕过授权高危漏洞”Bug。

核心的pom依赖内容如下:

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

模拟用户查询功能

我们知道在Shiro中可以在Realm实现授权和认证等功能,我们这里采用从数据中查询用户信息,比对密码。所以需要提供一个UserService来实现该功能。

当然,这里只是示例,不建议大家直接在数据库中存储密码。

先创建实体类:

public class User {

    private String username;

    private String password;

    // 省略getter/setter
}

对应的Service:

@Service
public class UserService {

    public User getUserByUserName(String username) {
        // 模拟返回,生产中不建议直接返回明文密码
        User user = new User();
        user.setUsername("secbro");
        user.setPassword("123456");
        return user;
    }
}

自定义Realm并实现功能

这里我们自定义一个Realm,在其中进行认证,关于授权部分默认返回null,也就是不校验权限。具体的权限校验,可根据业务需求,角色权限等设计进行校验。

@Component("loginRealm")
public class LoginRealm extends AuthorizingRealm {

    @Resource
    private UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 这里不做授权校验
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 用户登录时传入的用户名称
        String username = (String) token.getPrincipal();
        String password = new String((char[]) token.getCredentials());
        // 根据用户名查询用户信息
        User user = userService.getUserByUserName(username);
        if (user == null) {
            throw new UnknownAccountException("账户不存在!");
        }

        if (!password.equals(user.getPassword())) {
            throw new UnknownAccountException("密码错误!");
        }

        return new SimpleAuthenticationInfo(token.getPrincipal(), password, getName());
    }
}

这里直接将LoginRealm通过@Component进行实例化,在doGetAuthenticationInfo方法中进行用户名和密码的认证。

其中核心业务逻辑就是根据用户登录时传入的AuthenticationToken,获得传入的用户名和密码。

然后根据用户名查询数据库存储的用户信息,如果不存在或密码不一致则抛出异常。

最后,返回一个SimpleAuthenticationInfo对象,其中携带用户名、密码信息。

这里我们可以看出来,AuthenticationToken对象中的Principal相当于用户身份(唯一ID),而Credentials相当于密码,用来验证身份的。

集成Shiro配置

创建ShiroConfig类,使用@Configuration注解标注:

@Configuration
public class ShiroConfig {

    /**
     * 此处参数内的Realm已经通过类似的@Compenent进行初始化了
     */
    @Bean
    SecurityManager securityManager(Realm loginRealm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(loginRealm);
        return manager;
    }

    @Bean
    ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/index");
        bean.setUnauthorizedUrl("/unauthorizedurl");

        Map<String, String> map = new LinkedHashMap<>();
        map.put("/doLogin", "anon");
        map.put("/**", "authc");
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }
}

其中初始化SecurityManager时需要用到Realm,这里通过方法参数将前面实例的LoginRealm注入,创建SecurityManager时设置对应的Realm。

因为还需要对具体的Web请求进行拦截,因此还需要配置一个ShiroFilterFactoryBean,通过该类指定路径拦截规则,比如设置需要拦截的、不需要拦截的链接等。

其中,Map中配置了路径拦截规则,注意,要有序。

经过上面的配置,Shiro的集成已经完成。

测试案例

下面来写一个简单的Controller来验证一下Shiro的拦截功能。

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String hello() {
        System.out.println("Hello Shiro!");
        return "Hello Shiro!";
    }

    @RequestMapping("/login")
    public String toLogin() {
        return "please login!";
    }

    @RequestMapping("/doLogin")
    public void doLogin(String username, String password) {
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(new UsernamePasswordToken(username, password));
            System.out.println("用户:" + username + ",登录成功!");
        } catch (Exception e) {
            System.out.println("登录异常" + e.getMessage());
        }
    }
}

首先,直接访问/hello接口,由于未登录,直接跳转到/login当中,返回:

please login!

紧接着,访问/doLogin接口,传输用户名和密码,控制台打印:

用户:secbro,登录成功!

再次访问/hello接口,打印并返回:

Hello Shiro!

说明认证成功,并记录下认证成功的状态,后续可继续使用对应的功能了。

基于官方Starter整合

最后再提一下基于官方Starter的整合,其实整体来说只是把ShiroFilterFactoryBean的初始化从配置类移到了application.properties文件中。

首先,将shiro的依赖替换为starter的依赖:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
</dependency>

然后,去掉ShiroConfig中关于ShiroFilterFactoryBean的实例化方法,改为ShiroFilterChainDefinition:

@Bean
ShiroFilterChainDefinition shiroFilterChainDefinition(){
    DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
    definition.addPathDefinition("/doLogin","anon");
    definition.addPathDefinition("/**","authc");
    return definition;
}

在application.properties文件中添加如下配置:

# 是否允许将sessionId放到cookie中
shiro.sessionManager.sessionIdCookieEnabled=true
# 是否允许将sessionId放到Url地址拦中
shiro.sessionManager.sessionIdUrlRewritingEnabled=true
# 访问未获授权的页面时,默认的跳转路径
shiro.unauthorizedUrl=/unauthorizedurl
# 开启shiro
shiro.web.enabled=true
# 登录成功的跳转页面
shiro.successUrl=/index
# 登录页面
shiro.loginUrl=/login

其他的配置和操作与上面的示例一样,不再赘述。

项目源码:https://github.com/secbr/shiro/tree/main/springboot-shiro

小结

学习Shiro的关键点其实在于理解Realm的使用场景和原理。而关于Shiro在Spring Boot中的集成,如果有不太理解的地方,可回顾一下单纯使用Shiro的示例。Spring Boot的集成只不过是将之前new创建配置项纳入了Spring容器的管理而已。



Spring Boot整合Shiro,两种方式实战总结(含源码)插图1

关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台

除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接

本文链接:http://choupangxia.com/2021/01/27/spring-boot-shiro/