一、Shiro介绍
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。 他有三大核心组件:
- Subject
即“当前操作用户”。但是,在 Shiro 中,Subject 这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。Subject 代表了当前用户的安全操作,SecurityManager 则管理所有用户的安全操作。 - SecurityManager
它是Shiro 框架的核心,典型的 Facade 模式,Shiro 通过 SecurityManager 来管理内部组件实例,并通过它来提供安全管理的各种服务。 - Realm
Realm 充当了 Shiro 与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro 会从应用配置的 Realm 中查找用户及其权限信息。从这个意义上讲,Realm 实质上是一个安全相关的 DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给 Shiro。当配置 Shiro 时,你必须至少指定一个 Realm,用于认证和(或)授权。配置多个 Realm 是可以的,但是至少需要一个。Shiro 内置了可以连接大量安全数据源(又名目录)的 Realm,如 LDAP、关系数据库(JDBC)、类似 INI 的文本配置资源以及属性文件等。如果缺省的 Realm 不能满足需求,你还可以插入代表自定义数据源的自己的 Realm 实现。
二、SpringBoot 整合 Shiro
这里就以SpringBoot开发的一个博客项目中的登录模块为例,来展示与Shiro的整合,从而实现登录的认证。
-
1、引入Shiro相关依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
我的SpringBoot 版本是 2.6.4,Shiro版本自行选择。 -
2、login.html <form class="ui large form" th:action="@{/admin/login}" method="post" >
<div class="ui segment">
<div class="field">
<div class="ui left icon input">
<i class="user icon"></i>
<input type="text" name="username" placeholder="用户名">
</div>
</div>
<div class="field">
<div class="ui left icon input">
<i class="lock icon"></i>
<input type="password" name="password" placeholder="密码">
</div>
</div>
<button class="ui fluid large teal submit button">
登 录
</button>
</div>
<div class="ui error mini message"></div>
<div class="ui mini negative message" th:if="${message!=null}" th:text="${message}"></div>
</form>
登录页面表单部分,管理员输入用户名和密码后登录提交到 /admin/login 中去处理。 -
3、LoginController @Controller
@RequestMapping("/admin")
public class LoginController {
@Autowired
UserService userService;
@RequestMapping("/toLogin")
public String loginPage(){
return "admin/login";
}
@RequestMapping("/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model){
System.out.println("登录...............................\n\n");
System.out.println("登录用户名:" + username);
System.out.println("登录密码:" + password);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, MD5Utils.code(password));
try{
subject.login(token);
return "admin/index";
}catch (UnknownAccountException e){
model.addAttribute("message","用户名错误!");
return "admin/login";
}catch (IncorrectCredentialsException e){
model.addAttribute("message","密码错误!");
return "admin/login";
}
}
@RequestMapping("/toNoauth")
public String Unauthorized(){
return "admin/noauth";
}
}
将传过来的用户名和密码封装成登录数据 UsernamePasswordToken ,并使用shiro的subject组件自带的login方法,进行登录。 编写自定义的Realm,在Realm中主要工作就是授权和认证,连接真实数据库,根据刚封装token中的username查询用户是否存在,如果不存在,则会抛出UnknownAccountException 异常,如果存在,则继续将封装token中的密码与真实数据库中的密码比对,当然,密码校验shiro已经帮我们做了,不用我们自己写,如果比对不成功,则抛出IncorrectCredentialsException 异常,如果用户存在且密码比对成功,则 return “admin/index”; 成功进入首页。 这边还定义了当没有认证直接访问管理员首页及其它页面时,跳转的controller /toNoauth,logout登出功能shiro已经帮我们实现,不需要自己写。 -
4、自定义Realm UserRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了 -》 授权 doGetAuthorizationInfo");
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了 -》 认证 doGetAuthenticationInfo");
UsernamePasswordToken userToken = (UsernamePasswordToken)token;
System.out.println("userToken -> username" + userToken.getUsername());
System.out.println("userToken -> password" + userToken.getPassword());
User user = userService.queryUserByName(userToken.getUsername());
System.out.println("user ->" + user);
if(user == null){
return null;
}
Subject subject = SecurityUtils.getSubject();
subject.getSession().setAttribute("user",user);
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}
好了,到这一步,管理员登录认证已经完成,接下来也是最后一步,我们需要编写ShiroConfig配置类来决定哪些请求可以放行,哪些请求需要拦截认证,未认证/授权跳转的页面,登录成功跳转的页面等。 -
5、ShiroConfig @Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")
DefaultWebSecurityManager
securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
Map<String,String> filterMap = new LinkedHashMap<>();
filterMap.put("/admin/logout", "logout");
filterMap.put("/css/**", "anon");
filterMap.put("/images/**", "anon");
filterMap.put("/js/**", "anon");
filterMap.put("/admin/toLogin", "anon");
filterMap.put("/admin/login", "anon");
filterMap.put("/admin/**", "authc");
bean.setLoginUrl("/admin/toNoauth");
bean.setFilterChainDefinitionMap(filterMap);
bean.setSuccessUrl("admin/index");
bean.setUnauthorizedUrl("/admin/toNoauth");
return bean;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
@Bean(name = "userRealm")
public UserRealm userRealm(){
return new UserRealm();
}
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
}
-
6、测试 (1) 登录页面 (2)当没有登录认证,直接访问管理员首页 (3)当用户不存在 (4)当密码错误 (5)当用户名和密码正确 (6)登录成功后访问管理员首页 (7)登出 (8)登出后再访问管理员首页
|