获取源码和笔记地址:https://gitee.com/monologue_zsj/shiro.git (上篇文章地址:https://blog.csdn.net/weixin_42047611/article/details/114519074)
2、shiro-SpringBoot-jsp
server.port=8888
server.servlet.context-path=/shiro
spring.application.name=shiro
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/siasr?characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=123456
mybatis.type-aliases-package=com.monologue.entity
mybatis.mapper-locations=classpath:mapper/*.xml
logging.level.com.monologue.mapper=debug
<!--JSP 的依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--引入SpringBoot整合Shiro依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.5.3</version>
</dependency>
<%--resource/webapp/index.jsp(login.jsp,register.jsp):账户密码提交--%>
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<%@taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Index</title>
</head>
<body>
<h1>系统主页 v1.0</h1>
<%--主身份信息--%>
<h1><shiro:principal/></h1>
<shiro:authenticated>
认证之后展示的信息
</shiro:authenticated>
<shiro:notAuthenticated>
没有认证之后展示的信息
</shiro:notAuthenticated>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
<ul>
<shiro:hasAnyRoles name="user,admin">
<li><a href="">用户管理</a>
<ul>
<shiro:hasPermission name="user:add:*">
<li><a href="">insert</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:delete:*">
<li><a href="">delete</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:update:*">
<li><a href="">update</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:find:*">
<li><a href="">select</a></li>
</shiro:hasPermission>
</ul>
</li>
</shiro:hasAnyRoles>
<shiro:hasRole name="admin">
<li><a href="">商品管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">物流管理</a></li>
<li><a href="">系统管理</a></li>
</shiro:hasRole>
</ul>
</body>
</html>
/**
* Created by Monologue_zsj on 2021/3/7 14:38
* Author:小脸儿红扑扑
* Description:整合Shiro框架相关的配置类
*/
@Configuration
public class ShiroConfig {
//1、创建ShiroFilter (负责拦截所有请求)
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//给Filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//配置系统受限资源和系统公共资源
Map<String, String> map = new HashMap<>();
map.put("/user/login", "anon"); //anon 指定url可以匿名访问
map.put("/user/register", "anon"); //anon 指定url可以匿名访问
map.put("/register.jsp", "anon"); //anon 指定url可以匿名访问
map.put("/**", "authc"); //authc 请求这个资源需要认证和授权
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
//默认认证界面路径
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
return shiroFilterFactoryBean;
}
//2、创建安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") Realm realm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//给安全管理器设置Realm
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
//3、创建自定义Realm
@Bean
public Realm getRealm() {
CustomerRealm customerRealm = new CustomerRealm();
//修改凭证校验匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法为md5
credentialsMatcher.setHashAlgorithmName("MD5");
//设置散列次数
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
return customerRealm;
}
}
//service
@Override
public void register(User user) {
//明文密码进行MD5 + salt + hash散列
//1、生成随机盐
String salt = SaltUtils.getSalt(8);
//2、将随机盐保存的数据库
user.setSalt(salt);
Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
user.setPassword(md5Hash.toHex());
userMapper.save(user);
}
/**
* Created by Monologue_zsj on 2021/3/7 16:19
* Author:小脸儿红扑扑
* Description:根据beanName获取工厂中指定的bean对象
*/
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
public static Object getBean(String beanName) {
return context.getBean(beanName);
}
}
/**
* Created by Monologue_zsj on 2021/3/7 14:52
* Author:小脸儿红扑扑
* Description:自定义Realm
*/
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
//获取身份信息
String primaryPrincipal = (String) principal.getPrimaryPrincipal();
//根据主身份信息获取角色和权限信息(模拟数据)
if ("zsj".equals(primaryPrincipal)) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRole("user");
simpleAuthorizationInfo.addStringPermission("user:find:*");
return simpleAuthorizationInfo;
}
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("_________________________");
String principal = (String) token.getPrincipal();
//在工厂中获取service对象
UserService userService = (UserService) ApplicationContextUtils.getBean("userServiceImpl");
User user = userService.findByUserName(principal);
if (!ObjectUtils.isEmpty(user)) {
return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), this.getName());
}
return null;
}
}
常见过滤器
配置缩写 | 对应的过滤器 | 功能 |
---|---|---|
anon | AnonymousFilter | 指定url可以匿名访问 |
authc | FormAuthenticationFilter | 指定url需要form表单登录,默认会从请求中获取username,password,rememberMe 等参数并尝试登录,如果登录不了就会跳转到loginUrl配置的路径。我们也可以用这个过滤器做默认的登录逻辑,但是一般都是我们自己在控制器写登录逻辑的,自己写的话出错返回的信息都可以定制嘛。 |
authcBasic | BasicHttpAuthenticationFilter | 指定url需要basic登录 |
logout | LogoutFilter | 登出过滤器,配置指定url就可以实现退出功能,非常方便 |
noSessionCreation | NoSessionCreationFilter | 禁止创建会话 |
perms | PermissionsAuthorizationFilter | 需要指定权限才能访问 |
port | PortFilter | 需要指定端口才能访问 |
rest | HttpMethodPermissionFilter | 将http请求方法转化成相应的动词来构造一个权限字符串,这个感觉意义不大,有兴趣自己看源码的注释 |
roles | RolesAuthorizationFilter | 需要指定角色才能访问 |
ssl | SslFilter | 需要ssl请求才能访问 |
user | UserFilter | 需要已登录或“记住我”的用户才能访问 |
/**
* Created by Monologue_zsj on 2021/3/7 14:52
* Author:小脸儿红扑扑
* Description:自定义Realm
*/
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
//获取身份信息
String primaryPrincipal = (String) principal.getPrimaryPrincipal();
//根据主身份信息获取角色 和 权限信息
UserService userService = (UserService) ApplicationContextUtils.getBean("userServiceImpl");
User user = userService.findRolesByUsername(primaryPrincipal);
//根据主身份信息获取角色和权限信息
if (!CollectionUtils.isEmpty(user.getRoles())) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
user.getRoles().forEach(role -> {
simpleAuthorizationInfo.addRole(role.getName());
//权限信息
List<Permission> permissions = userService.findPermissionByRoleId(role.getId());
if (!CollectionUtils.isEmpty(permissions)) {
permissions.forEach(permission -> {
simpleAuthorizationInfo.addStringPermission(permission.getName());
});
}
});
return simpleAuthorizationInfo;
}
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("_________________________");
String principal = (String) token.getPrincipal();
//在工厂中获取service对象
UserService userService = (UserService) ApplicationContextUtils.getBean("userServiceImpl");
User user = userService.findByUserName(principal);
if (!ObjectUtils.isEmpty(user)) {
return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), this.getName());
}
return null;
}
}
/**
* Created by Monologue_zsj on 2021/3/7 15:22
* Author:小脸儿红扑扑
* Description:
*/
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/register")
public String register(User user) {
try {
userService.register(user);
return "redirect:/login.jsp";
} catch (Exception e) {
e.printStackTrace();
return "redirect:/register.jsp";
}
}
@RequestMapping("/login")
public String login(String username, String password) {
//获取主体对象
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username, password));
return "redirect:/index.jsp";
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("账户错误...");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误...");
}
return "redirect:/login.jsp";
}
@RequestMapping("/logout")
public String logout() {
SecurityUtils.getSubject().logout();
return "redirect:/login.jsp";
}
}
/**
* Created by Monologue_zsj on 2021/3/7 17:01
* Author:小脸儿红扑扑
* Description:
*/
@Controller
@RequestMapping("/order")
public class OrderController {
@RequestMapping("/save")
//@RequiresRoles("admin") //需要那个角色
//@RequiresPermissions("user:*:*") //用来判断权限字符串
public String save() {
//获取主体对象
//Subject subject = SecurityUtils.getSubject();
//代码方式
/*if (subject.hasRole("admin")) {
System.out.println("保存订单");
}else {
System.out.println("权限不足");
}
if (subject.isPermitted("user:*:*")) {
System.out.println("保存订单");
}else {
System.out.println("权限不足");
}*/
return "redirect:/index.jsp";
}
}
int save(User user);
@Select("select * from shiro_user where username = #{username}")
User findByUserName(String username);
//根据用户名查询角色
User findRolesByUsername(String username);
//根据角色id查询权限集合
List<Permission> findPermissionByRoleId(Integer id);
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.monologue.mapper.UserMapper">
<insert id="save" parameterType="User" keyProperty="id" useGeneratedKeys="true">
insert into shiro_user values (#{id}, #{username}, #{password}, #{salt})
</insert>
<resultMap id="UserMap" type="User">
<id column="uid" property="id" />
<result column="username" property="username" />
<!--角色信息-->
<collection property="roles" javaType="java.util.List" ofType="Role">
<id column="id" property="id" />
<result column="rname" property="name" />
</collection>
</resultMap>
<select id="findRolesByUsername" parameterType="String" resultMap="UserMap">
select u.id uid, u.username, r.id, r.name rname
from shiro_user u
left join t_user_role ur
on u.id = ur.userid
left join s_role r
on ur.roleid = r.id
where u.username = #{username}
</select>
<select id="findPermissionByRoleId" parameterType="Integer" resultType="Permission">
select p.id, p.name, p.url, r.name
from s_role r
left join t_role_permission rp
on r.id = rp.roleid
left join s_permission p
on rp.permissionid = p.id
where r.id = #{id}
</select>
</mapper>