Spring Boot整合Shiro,两种方式实战总结(含源码)
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依赖即可:
一路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容器的管理而已。
关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台
除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接