写在前面
自己在B站上看了一个shiro的视频,花了三四个小时速成了一下shiro,大概了解了一下shiro的流程,然后自己做了一个小Demo,记录一下。
正文
话不多说,直接上代码
- 首先是使用Maven搭建一下SSM的工程,做好准备工作
(如果有小伙伴不懂怎么使用Maven搭建SSM项目的话,可以参考我之前的博文https://blog.csdn.net/weixin_44215175/article/details/108642595) - 接着就是先引入shiro的相关依赖包,Maven的话在pom.xml中加入以下依赖:
<!--shiro版本-->
<shiro.version>1.3.2</shiro.version>
<!-- Shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-aspectj</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-hazelcast</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-guice</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
- 同时需要slf4j和ehcache,也一并引入
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!--ehcache-->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.8.3</version>
</dependency>
- 接下来就是配置shiro
在spring的配置文件中加入以下配置:
<!--
1.配置securityManager!
-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<!-- <property name="realm" ref="jdbcRealm"/>-->
<property name="authenticator" ref="authenticator" />
<property name="realms">
<list>
<ref bean="jdbcRealm" />
</list>
</property>
</bean>
<!--
2.配置cacheManager,缓存
2.1 需要加入ehcache的jar以及配置文件
-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<bean id="authenticator"
class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"/>
</property>
</bean>
<!--
3.配置realm
3.1 直接配置了实现了Realm接口的bean
-->
<bean id="jdbcRealm" class="pers.kuroko.realm.ShiroRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5" />
<property name="hashIterations" value="1024" />
<property name="storedCredentialsHexEncoded" value="true" />
</bean>
</property>
</bean>
<!--
4. 配置lifecycleBeanPostProcessor,可以自动地来调用配置在Spring Ioc容器中shiro bean的生命周期方法
-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!--
5. 启用IOC容器中使用shiro的注解,但必须配置lifecycleBeanPostProcessor之后才可以使用
-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!--
6. 配置shiroFilter
6.1 id必须和web.xml文件中配置的DelegatingFilterProxy的<filter-name>一致;
若不一致,则会抛出异常,因为shiro回来IoC容器中查找和<filter-name>名字对应的filter bean
6.2
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/view/login.jsp"/><!--登录页面-->
<property name="successUrl" value="/index.jsp"/><!--登录成功页面-->
<property name="unauthorizedUrl" value="/view/unauthorized.jsp"/><!--没有权限页面-->
<!-- <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap" />-->
<!--
配置哪些页面需要受保护
以及访问这些页面需要的权限.
1. anon 可以被匿名访问
2. authc 必须认证(登录)后才访问
3. logout 登出
3. 匹配原则是第一次优先匹配
4. roles 角色过滤器
-->
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap" />
</bean>
<!-- 配置一个bean,该bean实际上是一个Map,通过实例工厂方法的方式 -->
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder"
factory-method="buildFilterChainDefinitionMap" />
<bean id="filterChainDefinitionMapBuilder" class="pers.kuroko.factory.FilterChainDefinitionMapBuilder" />
这里我是采用配置一个filterChainDefinitionMap的方式来配置shiro的url拦截
- 创建FilterChainDefinitionMapBuilder
在这里配置具体的拦截方式
import java.util.LinkedHashMap;
public class FilterChainDefinitionMapBuilder {
public LinkedHashMap<String, String> buildFilterChainDefinitionMap() {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
// 应该操作数据库
map.put("/view/register.jsp", "anon");
map.put("/view/login.jsp", "anon");
map.put("/index/doLogin", "anon");
map.put("/index/doRegister", "anon");
map.put("/index/logout", "logout");
map.put("/view/admin.jsp", "roles[admin]");
map.put("/**", "authc");
return map;
}
}
- 编写自己的Realm
import org.apache.shiro.authc.*;
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.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import pers.kuroko.entity.User;
import pers.kuroko.service.UserService;
import java.util.HashSet;
import java.util.Set;
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
// 授权会被调用的方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 1. 从PrincipalCollection中来获取登录用户的信息
Object principal = principals.getPrimaryPrincipal();
User user = userService.selectUserByUsername((String) principal);
// 2. 利用登录的用户的信息来获取当前用户的角色或权限(查询数据库)
Set<String> roles = new HashSet<>();
roles.add(user.getRole());
// 3. 创建SimpleAuthenticationInfo对象,并设置其reles属性
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
// 4. 返回SimpleAuthenticationInfo对象
return info;
}
// 登录会被调用的方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token;
if (usernamePasswordToken.getUsername() == null || "".equals(usernamePasswordToken.getUsername())) {
return null;
}
String username = usernamePasswordToken.getUsername();
User user = userService.selectUserByUsername(username);
// 以下信息是从数据库中获取的
// 1) principle:认证的实体信息,可以是username,也可以是数据表对应的用户的实体类对象
Object principle = username;
// 2)credentials:密码
Object credentials = user.getUpwd();
// 3)realmName:当前realm 对象的 name,调用父类的getName()方法即可
String realmName = getName();
// 4)盐值
ByteSource credentialsSalt = ByteSource.Util.bytes(username);
return new SimpleAuthenticationInfo(principle, credentials, credentialsSalt, realmName);
}
}
- 编写Controller测试
@Controller("indexController")
@RequestMapping("/index")
public class IndexController {
@Autowired
private UserService userService;
Logger logger = LoggerFactory.getLogger(IndexController.class);
@RequestMapping("/doLogin")
public String doLogin(@RequestParam("uname") String uname,
@RequestParam("upwd") String upwd, Model model) {
Subject curUser = SecurityUtils.getSubject();
if (!curUser.isAuthenticated()) {
// 把用户名和密码封装成 UsernamePasswordToken对象
UsernamePasswordToken token = new UsernamePasswordToken(uname, upwd);
// rememberMe
token.setRememberMe(true);
try {
// 执行登录
// 执行了ShiroRealm中的doGetAuthenticationInfo方法
curUser.login(token);
}
// 所有认证时异常的父类
catch (AuthenticationException ae) {
logger.info("登录失败,失败原因:[{}]", ae.getMessage());
model.addAttribute("message", "用户名或密码错误");
return "login";
}
}
return "redirect:/index.jsp";
}
@RequestMapping("/doRegister")
public String doRegister(@RequestParam("uname") String uname,
@RequestParam("upwd") String upwd, Model model) {
if (userService.selectUserByUsername(uname) != null) {
model.addAttribute("message", "用户已存在");
return "register";
}
Object result = new SimpleHash("MD5", upwd, ByteSource.Util.bytes(uname), 1024);
User user = new User(uname, result.toString(), "user");
if (userService.addUser(user) > 0) {
return "redirect:login";
} else {
model.addAttribute("message", "注册失败");
return "register";
}
}
}
- 其他的Service和dao等都是简单的数据库操作
- 测试
运行结果:
登录成功后:
用admin登录,可以访问Admin页面
当使用其他用户登录时,由于没有访问Admin页面的权限,所以会跳转到无权限页面
写在后面
此Demo仅仅是简单演示了一下shiro实现的权限控制,真实使用到开发中还需要进行改进
本人仅仅是初学者,可能实现Demo不完善,觉得哪里不对的,还请大家多多指教
本项目完整Demo已上传GitHub,地址:https://github.com/Fjz-Kuroko/ShiroSSMDemo