一、环境准备
1、创建Maven项目,在pom.xml中添加依赖
org.apache.shiro
shiro-core
1.7.1
org.apache.shiro
shiro-spring
1.7.1
org.springframework
spring-webmvc
5.3.14
org.springframework
spring-jdbc
5.3.14
org.springframework
spring-context-support
5.3.14
org.mybatis
mybatis
3.5.7
org.mybatis
mybatis-spring
2.0.7
mysql
mysql-connector-java
8.0.27
2、配置数据源和MyBatis
在src/main/resources目录下创建一个名为applicationContext.xml的Spring配置文件,并配置数据源和MyBatis。
3、配置Shiro
在src/main/resources目录下创建一个名为shiro.ini的Shiro配置文件,并配置Shiro。
[main]
# Realm配置
myRealm = com.example.shiro.MyRealm
securityManager.realm = $myRealm
# 编码器配置
hashedCredentialsMatcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
hashedCredentialsMatcher.hashAlgorithmName = SHA-256
hashedCredentialsMatcher.hashIterations = 1
myRealm.credentialsMatcher = $hashedCredentialsMatcher
# 缓存配置
cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
securityManager.cacheManager = $cacheManager
ehcacheManager.cacheManagerConfigFile = classpath:ehcache.xml
cacheManager.cacheManager = $ehcacheManager
# 过滤器配置
authc = org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authc.loginUrl = /login.jsp
authc.successUrl = /index.jsp
authc.usernameParam = username
authc.passwordParam = password
authc.rememberMeParam = rememberMe
perms = org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
[urls]
# anon表示不需要认证
# authc表示需要认证
# perms表示需要权限
/login.jsp = anon
/logout = anon
/index.jsp = authc,perms[user:view]
/user/list = authc,perms[user:view]
/user/add = authc,perms[user:add]
/user/edit = authc,perms[user:edit]
/user/delete = authc,perms[user:delete]
3、编写MyRealm
在com.example.shiro
包下创建一个名为MyRealm的类,并继承org.apache.shiro.realm.AuthorizingRealm
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
User user = (User) principalCollection.getPrimaryPrincipal();
List permissions = userService.getPermissionsByUserId(user.getId());
authorizationInfo.addStringPermissions(permissions);
return authorizationInfo;
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
User user = userService.getUserByUsername(token.getUsername());
if (user == null) {
throw new UnknownAccountException();
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
return authenticationInfo;
}
}
4、编写UserService
在com.example.service包下创建一个名为UserService的接口,并定义获取用户信息和权限的方法。
public interface UserService {
User getUserByUsername(String username);
List getPermissionsByUserId(Long userId);
}
在com.example.service.impl包下创建一个名为UserServiceImpl的类,并实现UserService接口。
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private PermissionMapper permissionMapper;
@Override
public User getUserByUsername(String username) {
return userMapper.getUserByUsername(username);
}
@Override
public List getPermissionsByUserId(Long userId) {
return permissionMapper.getPermissionsByUserId(userId);
}
}
5、编写Controller
在com.example.controller包下创建一个名为UserController的类,并编写处理请求的方法。
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/list")
@RequiresPermissions("user:view")
public String list(Model model) {
List userList = userService.getUserList();
model.addAttribute("userList", userList);
return "user/list";
}
@RequestMapping("/add")
@RequiresPermissions("user:add")
public String add() {
return "user/add";
}
@RequestMapping("/edit/{id}")
@RequiresPermissions("user:edit")
public String edit(@PathVariable("id") Long id, Model model) {
User user = userService.getUserById(id);
model.addAttribute("user", user);
return "user/edit";
}
@RequestMapping("/update")
@RequiresPermissions("user:edit")
public String update(User user) {
userService.updateUser(user);
return "redirect:/user/list";
}
@RequestMapping("/delete/{id}")
@RequiresPermissions("user:delete")
public String delete(@PathVariable("id") Long id) {
userService.deleteUserById(id);
return "redirect:/user/list";
}
}
6、编写登录页面
在src/main/webapp下创建一个名为login.jsp的登录页面。
登录
记住我
登录
7、编写权限控制页面
在src/main/webapp下创建一个名为list.jsp的权限控制页面。
用户列表
编号
用户名
姓名
操作
${user.id}
${user.username}
${user.name}
编辑
删除
新增
8、配置ehcache.xml
在src/main/resources下创建一个名为ehcache.xml的Ehcache配置文件,并配置缓存。
9、编写Shiro配置文件
在src/main/resources下创建一个名为shiro.ini的Shiro配置文件,并配置相关信息。
[main]
# 配置Realm,用于身份认证和授权
userRealm = org.apache.shiro.realm.jdbc.JdbcRealm
userRealm.dataSource = $dataSource
userRealm.permissionsLookupEnabled = true
userRealm.authenticationQuery = SELECT password FROM user WHERE username = ?
userRealm.userRolesQuery = SELECT role_name FROM user_role WHERE username = ?
userRealm.permissionsQuery = SELECT permission FROM role_permission WHERE role_name = ?
userRealm.saltStyle = COLUMN
# 配置缓存
cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
cacheManager.cacheManagerConfigFile = classpath:ehcache.xml
# 配置Session管理器
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
sessionManager.globalSessionTimeout = 1800000
sessionManager.deleteInvalidSessions = true
# 配置Cookie管理器
cookie = org.apache.shiro.web.servlet.SimpleCookie
cookie.name = JSESSIONID
cookie.path = /
cookie.httpOnly = true
cookie.maxAge = 1800000
# 配置SecurityManager,管理所有的Subject
securityManager = org.apache.shiro.web.mgt.DefaultWebSecurityManager
securityManager.realm = $userRealm
securityManager.cacheManager = $cacheManager
securityManager.sessionManager = $sessionManager
securityManager.sessionManager.sessionIdCookie = $cookie
# 配置过滤器,用于拦截请求
authc = org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authc.loginUrl = /login
authc.successUrl = /user/list
authc.usernameParam = username
authc.passwordParam = password
authc.rememberMeParam = rememberMe
authc.rememberMeCookie = $cookie
perms = org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
# 配置ShiroFilter,用于管理过滤器链
shiroFilter = org.apache.shiro.web.servlet.ShiroFilter
shiroFilter.securityManager = $securityManager
shiroFilter.loginUrl = /login
shiroFilter.filterChainDefinitions = /user/list = authc, perms["user:view"]
/user/add = authc, perms["user:add"]
/user/edit/** = authc, perms["user:edit"]
/user/delete/** = authc, perms["user:delete"]
/** = anon
[urls]
# 配置不需要拦截的URL
/login = anon
/logout = anon
10、配置Spring容器
在src/main/resources下创建一个名为spring-context.xml的Spring配置文件,并配置相关信息。
/user/list = authc, perms["user:view"]
/user/add = authc, perms["user:add"]
/user/edit/** = authc, perms["user:edit"]
/user/delete/** = authc, perms["user:delete"]
/** = anon
11、编写Shiro的Realm
Shiro通过Realm来进行认证和授权操作。在这里,我们使用JdbcRealm来与数据库进行交互。
- 1、创建JdbcRealm类
package com.example.demo.shiro;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.HashSet;
import java.util.Set;
public class MyJdbcRealm extends JdbcRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
String password = getPasswordForUser(username);
if (password == null) {
throw new UnknownAccountException("No account found for user [" + username + "]");
}
return new SimpleAuthenticationInfo(username, password.toCharArray(), getName());
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
Set roles = getRolesForUser(username);
Set permissions = getPermissionsForUser(username);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
private Set getPermissionsForUser(String username) {
Set permissions = new HashSet();
// TODO: 获取该用户的所有权限,并加入到permissions集合中
return permissions;
}
private Set getRolesForUser(String username) {
Set roles = new HashSet();
// TODO: 获取该用户的所有角色,并加入到roles集合中
return roles;
}
private String getPasswordForUser(String username) {
// TODO: 根据用户名从数据库中获取密码
return null;
}
}
- 2、配置JdbcRealm
在applicationContext.xml文件中配置JdbcRealm。
二、编写Shiro的过滤器
Shiro提供了许多过滤器,用于实现不同的功能。在这里,我们需要用到以下过滤器:
- authc:需要进行身份验证才能访问
- perms:需要具有某种权限才能访问
- anon:不需要进行身份验证就能访问
1、(bug)创建ShiroFilterFactoryBean类
在com.example.demo.shiro包下创建一个ShiroFilterFactoryBean类。
package com.example.demo.shiro;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import java.util.LinkedHashMap;
import java.util.Map;
public class ShiroFilterFactoryBean extends ShiroFilterFactoryBean {
// 配置拦截链规则
@Override
protected Map createFilterChainDefinitionMap() {
Map filterChainDefinitionMap = new LinkedHashMap();
// 配置登录的url和登录成功的url
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/doLogin", "anon");
filterChainDefinitionMap.put("/logout", "logout");
// 配置不会被拦截的链接
filterChainDefinitionMap.put("/static/**", "anon");
// 配置拦截规则
filterChainDefinitionMap.put("/**", "authc");
return filterChainDefinitionMap;
}
// 配置登录成功之后跳转的url
public void setSuccessUrl(String successUrl) {
super.setSuccessUrl(successUrl);
}
// 配置登录页面的url
public void setLoginUrl(String loginUrl) {
super.setLoginUrl(loginUrl);
FormAuthenticationFilter filter = (FormAuthenticationFilter) super.getFilters().get("authc");
filter.setLoginUrl(loginUrl);
}
}
2、配置ShiroFilterFactoryBean
/login = anon
/logout = logout
/admin/** = authc,perms[admin:manage]
/** = authc
三、配置Spring MVC
在Spring MVC中,需要配置一个拦截器,用于在每个请求中进行身份验证和授权操作。
1、创建ShiroInterceptor类
在com.example.demo.shiro包下创建一个ShiroInterceptor类。
package com.example.demo.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ShiroInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestUri = request.getRequestURI();
if (requestUri.equals("/login") || requestUri.equals("/doLogin")) {
return true;
}
Subject subject = SecurityUtils.getSubject();
if (!subject.isAuthenticated()) {
response.sendRedirect("/login");
return false;
}
String permission = getPermission(requestUri);
if (!subject.isPermitted(permission)) {
response.sendRedirect("/unauthorized");
return false;
}
return true;
}
private String getPermission(String requestUri) {
// TODO: 根据请求URI获取相应的权限字符串
return "";
}
}
2、配置ShiroInterceptor
在spring-mvc.xml文件中配置ShiroInterceptor。
至此,SSM框架整合Shiro的配置就完成了。可以启动应用程序并尝试访问受保护的URL来测试配置是否正确。
四、使用Shiro
1、创建用户登录页面
在src/main/resources/templates目录下创建一个名为login.html的文件,用于用户登录。
登录页面
用户名:
密码:
2、创建未授权页面
在src/main/resources/templates目录下创建一个名为unauthorized.html的文件,用于在未经授权的情况下拒绝访问。
未授权页面
对不起,您无权访问该页面!
3、创建受保护的页面
在src/main/resources/templates目录下创建一个名为admin.html的文件,用于测试受保护的页面。
管理员页面
欢迎来到管理员页面!
4、创建Controller
在com.example.demo.controller包下创建一个名为AdminController的控制器类。
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/admin")
public class AdminController {
@RequestMapping("/index")
public String index() {
return "admin";
}
}
5、测试
启动应用程序并尝试访问以下URL:
/login
:进入登录页面;
/admin/index
:进入受保护的页面,需要登录和具有admin:manage权限才能访问;
/unauthorized
:访问未经授权的页面。
在登录页面输入用户名和密码,可以成功登录并进入受保护的页面;在未经授权的情况下访问受保护的页面,会重定向到未授权页面。
五、自定义Realm
在前面的示例中,我们使用了IniRealm来实现身份验证和授权,这种方式非常简单,但是不太灵活。在实际应用中,我们可能需要自定义Realm来适应特定的需求。
1、创建自定义Realm
在com.example.demo.shiro包下创建一个名为CustomRealm的类,继承AuthorizingRealm类并实现其中的两个方法。
package com.example.demo.shiro;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class CustomRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
String username = (String) getAvailablePrincipal(principals);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
if ("admin".equals(username)) {
info.addRole("admin");
info.addStringPermission("admin:manage");
}
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
if (!"admin".equals(username)) {
throw new UnknownAccountException("Unknown account " + username);
}
if (!"123456".equals(password)) {
throw new IncorrectCredentialsException("Incorrect password for account " + username);
}
return new SimpleAuthenticationInfo(username, password, getName());
}
}
上述代码中的doGetAuthenticationInfo()方法用于实现身份验证,其实现方式与前面的示例类似;doGetAuthorizationInfo()方法用于实现授权,这里根据用户名判断用户是否为管理员,如果是,则为其添加一个名为admin的角色和一个名为admin:manage的权限。
2、在Shiro中使用自定义Realm
修改ShiroConfig类中的securityManager()方法,将其中的IniRealm替换为CustomRealm。
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm());
return securityManager;
}
@Bean
public CustomRealm customRealm() {
return new CustomRealm();
}
现在,我们已经成功创建了一个自定义的Realm并将其集成到Shiro中,可以重新启动应用程序并进行测试。
3、测试
重新启动应用程序并尝试访问以下URL:
/login
:进入登录页面;
/admin/index
:进入受保护的页面,需要登录和具有admin:manage权限才能访问;
/unauthorized
:访问未经授权的页面。
在登录页面输入用户名和密码,可以成功登录并进入受保护的页面;在未经授权的情况下访问受保护的页面,会重定向到未授权页面。
六、结语
本文介绍了如何将Shiro集成到SSM框架中,并通过一个简单的示例演示了Shiro的身份验证和授权功能,以及如何自定义Realm来实现特定的需求。
当然,Shiro的功能远不止于此,它还提供了许多其他的功能和特性,如Session管理、RememberMe功能、加密和解密、过滤器等,读者可以参考官方文档深入了解。同时,由于Shiro的灵活性和可扩展性,也可以通过扩展其他组件,如Cache、Realms、SessionDAO等,来满足更多的需求。
在实际开发中,安全性通常是不容忽视的一个问题,Shiro作为一个功能丰富、易于使用、灵活可扩展的安全框架,在保护应用程序方面具有重要作用。