视频来源 编程不良人Shiro
1. 权限管理
1.1 什么是权限管理
基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户只能访问自己被授权的资源。
权限管理包括身份认证和授权两部分,简称认证授权
。对于需要访问系统资源的用户先进行身份认证,认证通过后再看该用户具有的权限是否能访问该资源。
1.2 身份认证 authentication
就是判断一个用户是否为合法用户的过程,最简单的身份认证就是用户在系统中输入用户名和密码,是否和系统中该用户的信息一致,来判断身分是否正确。
1.3 授权 authorization
授权,即访问控制,控制用户能够访问哪些资源。
2. Shiro
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
Shiro是一款功能强大的且简单易用的Java安全框架,它执行身份认证,授权,加密和会话管理。使用Shiro易于理解的API,你可以快速轻松上手保护任何应用程序,从最小的移动应用到最大的web和企业应用。
总结:shiro是apache下的一款开源框架,它将软件系统的安全认证功能抽离出来,实现了身份认证、授权、加密、会话管理等功能,组成了一个通用的安全框架。
- 主体 (org.apache.shiro.subject.Subject)
当前与软件交互的实体(用户,第三方服务,cron 作业等)的特定于安全性的“视图”。 - SecurityManager (org.apache.shiro.mgt.SecurityManager)
如上所述,SecurityManager
是 Shiro 架构的核心。它主要是一个“伞”对象,用于协调其托管组件以确保它们能够顺利协同工作。它还 ManagementShiro 对每个应用程序用户的视图,因此它知道如何对每个用户执行安全性操作。 - 认证者 (org.apache.shiro.authc.Authenticator)
Authenticator
是负责执行用户的身份验证(登录)并对其作出反应的组件。当用户尝试登录时,该逻辑由Authenticator
执行。Authenticator
知道如何与一个或多个存储相关用户/帐户信息的Realms
进行协调。从这些Realms
获得的数据用于验证用户的身份,以确保用户确实是他们所说的真实身份。 - 认证策略 (org.apache.shiro.authc.pam.AuthenticationStrategy)
如果配置了多个Realm
,则AuthenticationStrategy
将协调领域以确定身份验证尝试成功或失败的条件(例如,如果一个领域成功但其他领域失败,则该尝试成功吗?是否所有领域都必须成功?第一?)。 - 授权人 (org.apache.shiro.authz.Authorizer)
Authorizer
是负责确定应用程序中用户访问控制的组件。它是最终表明是否允许用户做某事的机制。与Authenticator
一样,Authorizer
也知道如何与多个后端数据源进行协调以访问角色和权限信息。Authorizer
使用此信息来确定是否允许用户执行给定的动作。 - SessionManager (org.apache.shiro.session.mgt.SessionManager)
SessionManager
知道如何创建和 Management 用户Session
的生命周期,以便为所有环境中的用户提供可靠的会话体验。这是安全框架领域中的一项独特功能-Shiro 能够在任何环境中本地 Management 用户会话,即使没有 Web/Servlet 或 EJB 容器也可以。默认情况下,Shiro 将使用现有的会话机制(例如 Servlet 容器)(如果可用),但是如果没有这种机制(例如在独立应用程序或非 Web 环境中),它将使用其内置的企业会话 Management 来提供相同的编程经验。SessionDAO
的存在是为了允许使用任何数据源来保留会话。 - SessionDAO (org.apache.shiro.session.mgt.eis.SessionDAO)
SessionDAO
代表SessionManager
执行Session
持久性(CRUD)操作。这允许将任何数据存储插入会话 Management 基础结构。 - CacheManager (org.apache.shiro.cache.CacheManager)
CacheManager
创建和 Management 其他 Shiro 组件使用的Cache
实例生命周期。由于 Shiro 可以访问许多后端数据源以进行身份验证,授权和会话 Management,因此缓存一直是框架中的一流架构功能,可以在使用这些数据源时提高性能。可以将任何现代的开源和/或企业缓存产品插入 Shiro,以提供快速有效的用户体验。 - 密码学 (org.apache.shiro.crypto.*)
密码术是企业安全框架的自然补充。 Shiro 的crypto
软件包包含易于使用和理解的密码学密码,哈希(又名摘要)和不同编解码器实现的表示形式。该软件包中的所有类都经过精心设计,以使其易于使用和理解。使用 Java 的本机加密技术支持的任何人都知道,驯服它可能是具有挑战性的动物。 Shiro 的加密 API 简化了复杂的 Java 机制,并使加密技术易于普通凡人使用。 - Realm (org.apache.shiro.realm.Realm)
如上所述,领域充当 Shiro 与应用程序安全数据之间的“bridge 梁”或“连接器”。当 true 需要与安全性相关的数据(例如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro 会从一个或多个为应用程序配置的领域中查找许多此类内容。您可以根据需要配置多个Realms
(通常每个数据源一个),并且 Shiro 会根据需要与它们进行协调,以进行身份验证和授权。
3. 第一个用户认证程序
认证大体流程
使用springboot搭建
1. 先创建一个安全管理器 DefaultSecurityManager
2. 定义好realm(就是ini文件信息)
3. 给安全管理器设置一个realm
4. 给全局工具类(SecurityUtils)设置全局管理器(DefaultSecurityManager)
5. 通过全局工具类获取一个主体(Subject)
6. 封装安全令牌(UsernamePasswordToken),构造传入用户名、密码等信息
7. 主体验证令牌
编写shiro.ini
在shiro.ini中存储用户信息
[users]
lyk=123456
ljj=123456
main.java
public static void main(String[] args) {
//1:创建安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2:给安全管理器设置 Realm
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
securityManager.setRealm(iniRealm);
//3:给全局工具类设置全局管理器
SecurityUtils.setSecurityManager(securityManager);
//3:获取主体对象
Subject subject = SecurityUtils.getSubject();
//5:创建安全令牌
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "123456");
usernamePasswordToken.setRememberMe(true);
try {
subject.login(usernamePasswordToken);
} catch (UnknownAccountException e) {
System.out.println("账户错误:" + e);
} catch (IncorrectCredentialsException e) {
System.out.println("密码错误:" + e);
}
}
用户名错误会报错误 UnknownAccountException
密码错误会报错误 IncorrectCredentialsException
4. 认证源码
最终执行用户名比较是在SimpleAccountRealm
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken)token;
SimpleAccount account = this.getUser(upToken.getUsername());
if (account != null) {
if (account.isLocked()) {
throw new LockedAccountException("Account [" + account + "] is locked.");
}
if (account.isCredentialsExpired()) {
String msg = "The credentials for account [" + account + "] are expired";
throw new ExpiredCredentialsException(msg);
}
}
最终执行密码校验是在AuthorizingRealm
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = this.getCredentialsMatcher();
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) {
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg);
}
} else {
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify credentials during authentication. If you do not wish for credentials to be examined, you can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
}
}
分析完源码,我们发现我们可以自定义Realm,覆盖认证和授权的操作
package com.kang.realm;
public class MyRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}
5. MD5加密
MD5 算法简介:
1)作用:一般用来加密、签名。
2)特点:MD5 算法不可逆,内容相同,无论执行多少次,结果始终一样。
3)结果:始终是一个 16进制,32位长度的字符串。
// md5
Md5Hash md5Hash = new Md5Hash("123123");
System.out.println(md5Hash.toHex());
// md5+盐
md5Hash = new Md5Hash("123123","avcdrt");
System.out.println(md5Hash.toHex());
// md5+盐+hash散列
md5Hash = new Md5Hash("123123","avcdrt",1024);
System.out.println(md5Hash.toHex());
使用方法
在自定义Realm中存放加密数据
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = (String) token.getPrincipal();
System.out.println(principal);
if ("张三".equals(principal)) {
return new SimpleAuthenticationInfo("张三",
"5194528bd63abca4bf9d09885bb425b7",
ByteSource.Util.bytes("swfa"),
this.getName());
}
return null;
}
然后设置hash算法,更改Realm的默认加密处理
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5");
matcher.setHashIterations(1024);
//放入hash算法规则
myMd5Realm.setCredentialsMatcher(matcher);
自动生成Salt工具类
package com.kang.shiro;
import java.util.Random;
public class CodeUtil {
private final static String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-*/+";
private final static String num = "0123456789";
/**
* 生成盐
*
* @return
*/
public static String salt() {
String uuid = new String();
for (int i = 0; i < 6; i++) {
char ch = str.charAt(new Random().nextInt(str.length()));
uuid += ch;
}
return uuid;
}
public static void main(String[] args) {
System.out.println(salt());
}
}
6. 授权
先认证,再授权
授权方式
基于角色的访问控制
subject.hasRole("admin"){
//admin用户可以访问的页面
}
基于资源的访问控制
if (subject.isPermitted("admin:update:1")){
//admin修改1号用户的权限
}
权限字符串的规则是:资源标识符:操作:资源实例标识符
,意思是对哪个资源的哪个实例具体有什么操作,可以使用*进行通配。
对于用户的创建权限 : user:create 或者 user:create:*
用户修改实例001的权限: user:update:001
用户修改实例001的所有权限: user:*:001
Shiro给主体授权的操作
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String primaryPrincipal = (String) principals.getPrimaryPrincipal();
HashSet<String> hashSet = new HashSet<>();
//给主体赋角色
hashSet.add("admin");
hashSet.add("user");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(hashSet);
//赋资源操作
simpleAuthorizationInfo.addStringPermission("user:*:001");
return simpleAuthorizationInfo;
}
Shiro中验证身份方式
编程式
subject.hasRole("admin"){
//admin用户可以访问的页面
}
if (subject.isPermitted("admin:update:1")){
//admin修改1号用户的权限
}
注解式
@RequiresRoles() 基于角色
@RequiresPermissions() 基于资源
//这两个有一个就行了
public void a(){
//有权限才会进入
}
标签式
<shiro:hasPermission name="user:add:*">
内容
</shiro:hasPermission>
<shiro:hasRole name="admin">
内容
</shiro:hasRole>
7. SpringBoot整合Shiro
增加maven依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
yaml配置
mvc:
view:
prefix: /
suffix: .jsp
创建ShiroConfigjava.java
只需要在配置类中注入三个核心对象
- ShiroFilterFactoryBean
Shiro各类滤器的指定功能
- DefaultWebSecurityManager
- Realm
package com.kang.config;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
@Bean
public Realm getRealm(){
MyRealm myRealm = new MyRealm();
return myRealm;
}
}
自定义Realm
MyReam.java
/自定义Realm
public class MyRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
return null;
}
}
login.jsp
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>登陆页面</title>
</head>
<body>
<h1>我的登陆页面</h1>
<form action="" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
<input type="submit" value="登陆">
</form>
</body>
</html>
编写实体类entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Permission implements Serializable {
private String id;
private String name;
private String url;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role implements Serializable {
private String id;
private String name;
private List<Permission> permissions;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private int id;
private String username;
private String password;
private String salt;
private List<Role> roles;
}
运行查看结果:
认证和退出
修改Realm
//自定义Realm
public class MyRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = (String) token.getPrincipal();
if ("lyk".equals(principal)) {
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, "123123", this.getName());
return authenticationInfo;
}
return null;
}
}
写Controller方法
UserController.java
@Controller
@RequestMapping("/user")
public class UserController {
@PostMapping(value = "/login")
public String login(String username, String password) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
try {
subject.login(usernamePasswordToken);
return "redirect:/index.jsp";
} catch (UnknownAccountException e) {
System.out.println("账号错误");
} catch (IncorrectCredentialsException e) {
System.out.println("密码错误");
}
return "redirect:/login.jsp";
}
}
退出登录
编写Controller
UserController.java
@Controller
@RequestMapping("/user")
public class UserController {
@GetMapping(value = "/logout")
public String logout() {
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated()) {
subject.logout();
}
return "redirect:/login.jsp";
}
}
index.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<!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>菜单页面</title>
</head>
<body>
<h1>菜单页面</h1>
<ul>
<li><a href="">用户管理</a></li>
<li><a href="">订单管理</a></li>
<li><a href="">商品管理</a></li>
<li><a href="">物流管理</a></li>
<li><a href="${pageContext.request.contextPath}/user/logout">退出登陆</a></li>
</ul>
</body>
mybatis连接数据库
数据库设计
sql文件
/*
SQLyog Ultimate v10.00 Beta1
MySQL - 5.5.62 : Database - bilibili
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`bilibili` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci */;
USE `bilibili`;
/*Table structure for table `permission` */
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) DEFAULT '' COMMENT '名称',
`url` varchar(128) DEFAULT '' COMMENT '接口路径',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*Data for the table `permission` */
insert into `permission`(`id`,`name`,`url`) values (1,'user:*:*',''),(2,'product:*:01',''),(3,'order:*:*','');
/*Table structure for table `role` */
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(128) DEFAULT '' COMMENT '名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*Data for the table `role` */
insert into `role`(`id`,`name`) values (1,'admin'),(2,'user'),(3,'product');
/*Table structure for table `role_permission` */
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`role_id` bigint(20) DEFAULT NULL,
`permission_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
/*Data for the table `role_permission` */
insert into `role_permission`(`id`,`role_id`,`permission_id`) values (1,1,1),(2,1,2),(3,2,1),(4,3,2),(5,1,3);
/*Table structure for table `user` */
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(10) NOT NULL AUTO_INCREMENT,
`username` varchar(155) COLLATE utf8_unicode_ci DEFAULT NULL,
`password` varchar(155) COLLATE utf8_unicode_ci DEFAULT NULL,
`salt` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
/*Data for the table `user` */
insert into `user`(`id`,`username`,`password`,`salt`) values (1,'admin','032fc908968ed4de4d917d15725972ed','\'NUH'),(2,'zhangsan','9181fa5aed0d237eb9b3b57952709d2e','ZPP/');
/*Table structure for table `user_role` */
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`role_id` bigint(20) DEFAULT NULL,
`user_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
/*Data for the table `user_role` */
insert into `user_role`(`id`,`role_id`,`user_id`) values (1,1,1),(2,2,2),(3,3,2),(4,2,1);
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
增加maven配置
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<properties>
<mysql.version>5.5.62</mysql.version>
</properties>
applicationContext.yaml
修改SpringBoot配置文件
spring:
datasource:
url: jdbc:mysql://localhost:3306/bilibili?useUnicode=true&characterEncodind=utf8&useSSL=true&serverTimezone=GMT
username: root
password: 123123
driver-class-name: com.mysql.jdbc.Driver
mybatis:
type-aliases-package: com.kang.entity
mapper-locations: classpath:mybatis/mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
基于MD5+Salt的注册功能
adduser.jsp
添加用户页面
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>添加用户</title>
</head>
<body>
<h1>添加用户</h1>
<form action="${pageContext.request.contextPath}/user/adduser" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
<input type="submit" value="登陆">
</form>
</body>
</html>
UserDao.java
public interface UserDao {
int addUser(User user);
}
UserMapper.xml
<?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.kang.dao.UserDao">
<insert id="addUser">
insert into user (username, password, salt)
values (#{username}, #{password}, #{salt})
</insert>
</mapper>
UserService.java
@Service("userService")
public class UserServiceImpl implements UserService{
@Autowired
@Qualifier("userDao")
private UserDao userDao;
@Override
public int addUser(User user) {
String salt = SaltUtils.getSalt(4);
user.setSalt(salt);
Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
user.setPassword(md5Hash.toHex());
return userDao.addUser(user);
}
}
UserController
@PostMapping("/adduser")
public String addUser(User user){
try {
userService.addUser(user);
return "redirect:/login.jsp";
}catch (Exception e){
e.printStackTrace();
return "redirect:/adduser.jsp";
}
}
基于MD5+Salt的登录功能
UserDao.java
User findUserByName(String name);
UserMapper.xml
<?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.kang.dao.UserDao">
<select id="findUserByName" resultType="user">
select *
from user
where username = #{username}
</select>
</mapper>
…省略Service层
UserController
@PostMapping("/login")
public String login(String username, String password){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
return "redirect:/index.jsp";
}catch (UnknownAccountException e){
System.out.println(e.getMessage());
System.out.println("账号不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}
}
MyReam.java
修改Realm的doGetAuthenticationInfo
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String) authenticationToken.getPrincipal();
UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
User user = userService.findUserByName(principal);
if (!StringUtils.isEmpty(user)) {
return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), new MyByteSource(user.getSalt()), this.getName());
}
return null;
}
这里注意一个细节,MyReam这个类是没有注入到容器里面的,所以不可以直接注入UserService这个类
这里使用一个工具类用来获取工厂里的对象
ApplicationContextUtil.java
package com.kang.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@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);
}
}
最后一步,也是最重要的一步
在ShiroConfig.java
中增加对Realm的配置
@Bean
public Realm getRealm(){
MyRealm myRealm = new MyRealm();
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashIterations(1024);
matcher.setHashAlgorithmName("md5");
myRealm.setCredentialsMatcher(matcher);
return myRealm;
授权功能
基于角色授权
编写dao层
//根据用户名查询所有的角色
User findRolesByName(String username);
<?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.kang.dao.UserDao">
<resultMap id="userMap" type="User">
<id column="uid" property="id"/>
<result column="username" property="username"/>
<collection property="roles" javaType="list" ofType="Role">
<id column="rid" property="id"/>
<result column="rname" property="name"/>
</collection>
</resultMap>
<select id="findRolesByName" resultMap="userMap">
SELECT u.`id` uid,
u.`username`,
r.`id` rid,
r.`name` rname
FROM `role` r
LEFT JOIN `user_role` ur
ON r.`id` = ur.role_id
LEFT JOIN `user` u
ON ur.user_id = u.`id`
WHERE u.`username` = #{username}
</select>
</mapper>
编写业务层
这里就不写了~~~
编写MyRealm
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
//从数据库中根据用户名查找到的角色信息
//并把查询道德所有角色信息增加到主体上
User user = userService.findRolesByName(primaryPrincipal);
if (!CollectionUtils.isEmpty(user.getRoles())) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
user.getRoles().forEach(role -> {
simpleAuthorizationInfo.addRole(role.getName());
});
return simpleAuthorizationInfo;
}
return null;
}
基于资源权限授权
编写dao层
//根据角色id查询权限信息的方法
List<Permission> findPermissionsById(String 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.kang.dao.UserDao">
<select id="findPermissionsById" parameterType="String" resultType="Permission">
SELECT p.id, p.name
FROM role r
LEFT JOIN `role_permission` rp ON r.`id` = rp.role_id
LEFT JOIN permission p ON p.id = rp.permission_id
WHERE r.`id` = #{id}
</select>
</mapper>
Service层
这一层就省略不写了~~~
编写MyRealm
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
User user = userService.findRolesByName(primaryPrincipal);
if (!CollectionUtils.isEmpty(user.getRoles())) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
user.getRoles().forEach(role -> {
simpleAuthorizationInfo.addRole(role.getName());
//一个用户可能存在多个角色
//然后基于每一个角色,查找这个角色所拥有的权限信息,赋值用户主体
List<Permission> permissions = userService.findPermissionsById(role.getId());
if (!CollectionUtils.isEmpty(permissions)){
permissions.forEach(permission -> {
simpleAuthorizationInfo.addStringPermission(permission.getName());
});
}
});
return simpleAuthorizationInfo;
}
return null;
}
8. 整合Redis
-
开启Redis服务
-
去appicationContext中去配置 redsi
Spring:
redis:
port: 6379
host: 192.168.237.135
database: 0
编写RedisCacheManager
public class RedisCacheManager implements CacheManager {
/**
*
* @param cachename 认证或者授权缓存的统一名称
*/
@Override
public <K, V> Cache<K, V> getCache(String cachename) throws CacheException {
return new RedisCache<>(cachename);
}
}
编写Chcahe
//自定义redis缓存的实现
public class RedisCache<k, v> implements Cache<k, v> {
private String cacheName;
public RedisCache() {
}
public RedisCache(String cacheName) {
this.cacheName = cacheName;
}
@Override
public v get(k k) throws CacheException {
System.out.println("get key " + k);
return (v) getRedisTemplate().opsForHash().get(cacheName, k.toString());
}
@Override
public v put(k k, v v) throws CacheException {
System.out.println("put key" + k);
System.out.println("put value" + v);
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.opsForHash().put(cacheName, k.toString(), v);
return null;
}
@Override
public v remove(k k) throws CacheException {
return (v) getRedisTemplate().opsForHash().delete(cacheName, k.toString());
}
@Override
public void clear() throws CacheException {
getRedisTemplate().opsForHash().delete(cacheName);
}
@Override
public int size() {
return getRedisTemplate().opsForHash().size(cacheName).intValue();
}
@Override
public Set<k> keys() {
return getRedisTemplate().opsForHash().keys(cacheName);
}
@Override
public Collection<v> values() {
return getRedisTemplate().opsForHash().values(cacheName);
}
private RedisTemplate getRedisTemplate() {
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
在ShiroConfig中开启缓存管理
@Bean
public Realm getRealm(){
MyRealm myRealm = new MyRealm();
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashIterations(1024);
matcher.setHashAlgorithmName("md5");
myRealm.setCredentialsMatcher(matcher);
// 开启缓存管理
myRealm.setCacheManager(new RedisCacheManager());
myRealm.setCachingEnabled(true);
myRealm.setAuthenticationCacheName("authenticationCache");
myRealm.setAuthenticationCachingEnabled(true);
myRealm.setAuthorizationCacheName("authorizationCache");
myRealm.setAuthorizationCachingEnabled(true);
return myRealm;
}
9. 登录验证码
ImageUtil.java
/**
* @title 验证码工具类
* @author boat
*/
public class ImageUtil {
private static int width = 90;// 定义图片的width
private static int height = 30;// 定义图片的height
//5个验证码一组
/* private static int codeCount = 5;// 定义图片上显示验证码的个数
private static int xx = 13;*/
//4个验证码一组
private static int codeCount = 4;// 定义图片上显示验证码的个数
private static int xx = 15;
private static int codeY = 23;
private static int fontHeight = 22;
private static char[] codeSequence = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t', 'u','v','w','x','y','z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
/**
* 生成一个map集合
* code为生成的验证码
* codePic为生成的验证码BufferedImage对象
*
* @return
*/
public static Map<String, Object> generateCodeAndPic() {
// 定义图像buffer
BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// Graphics2D gd = buffImg.createGraphics();
// Graphics2D gd = (Graphics2D) buffImg.getGraphics();
Graphics gd = buffImg.getGraphics();
// 创建一个随机数生成器类
Random random = new Random();
// 将图像填充为白色
gd.setColor(Color.WHITE);
gd.fillRect(0, 0, width, height);
// 创建字体,字体的大小应该根据图片的高度来定。
Font font = new Font("Arial", Font.BOLD, fontHeight);
// 设置字体。
gd.setFont(font);
// 画边框。
//gd.setColor(Color.LIGHT_GRAY);
gd.setColor(getRandomColor());
gd.drawRect(0, 0, width - 1, height - 1);
// 随机产生40条干扰线,使图象中的认证码不易被其它程序探测到。
gd.setColor(getRandomColor());
for (int i = 0; i < 30; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
gd.drawLine(x, y, x + xl, y + yl);
}
// randomCode用于保存随机产生的验证码,以便用户登录后进行验证。
StringBuffer randomCode = new StringBuffer();
// 随机产生codeCount数字的验证码。
for (int i = 0; i < codeCount; i++) {
// 得到随机产生的验证码数字。
String code = String.valueOf(codeSequence[random.nextInt(codeSequence.length)]);
// 用随机产生的颜色将验证码绘制到图像中。
gd.setColor(getRandomColor());
gd.drawString(code, (i + 1) * xx, codeY);
// 将产生的四个随机数组合在一起。
randomCode.append(code);
}
Map<String, Object> map = new HashMap<String, Object>();
//存放验证码
map.put("code", randomCode);
//存放生成的验证码BufferedImage对象
map.put("codePic", buffImg);
return map;
}
/**
* 随机取色
*/
public static Color getRandomColor() {
Random ran = new Random();
int red = ran.nextInt(256);
int green = ran.nextInt(256);
int blue = ran.nextInt(256);
Color color = new Color(red,green,blue);
return color;
}
// 获取验证码
public static Map<String,Object> getCheckCodePic(){
return ImageUtil.generateCodeAndPic();
}
public static void main(String[] args) throws Exception {
//创建文件输出流对象
OutputStream out = new FileOutputStream("d://"+System.currentTimeMillis()+".jpg");
Map<String,Object> map = ImageUtil.generateCodeAndPic();
ImageIO.write((RenderedImage) map.get("codePic"), "jpeg", out);
System.out.println("验证码的值为:"+map.get("code"));
}
}
然后写一个Controller方法调用
@GetMapping("verify")
@ResponseBody
public String vertify(HttpServletResponse response, HttpSession session) throws IOException {
Map<String, Object> checkCodePic = ImageUtil.getCheckCodePic();
//获取一个输出流
ServletOutputStream outputStream = response.getOutputStream();
//获取验证码信息,并存入到Session中,以便验证
String stringCode = checkCodePic.get("code").toString();
session.setAttribute("code", stringCode);
//流传输一个图片验证码
ImageIO.write((RenderedImage) checkCodePic.get("codePic"), "jpeg", outputStream);
return null;
}