Shiro实战教程笔记

1. 权限管理

1.1 什么是权限管理

基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或安全策略控制用户可以访问并且只能访问被授权的资源。

1.2 什么是身份认证

身份认证 就是判断一个用户是否为合法用户的过程,最简单方式就是根据用户输入的用户名和密码,和系统存储层一致不,来判断用户身份是否正确。

1.3 什么是身份授权

身份授权 即访问控制,控制谁能访问那些资源,不同的用户应该拥有不同的资源访问权限,常见的有学校的教务管理系统:有教师,学生,管理员登录几个模块,以不同的身份登录就会显示不同的界面!

2. 什么是shiro

shiro是Apache旗下的一个开源框架,它将软件系统的安全认证相关的功能抽取出来,是一个功能强大且简单易用的Java安全框架,它可以用来进行身份验证,授权,加密和会话管理。

3. shiro核心架构

3.1 Subject

Subject即主体,外部应用与Subject进行交互,Subject对象记录了当前操作的用户,将用户的概念理解成当前操作的主体,可能是一个浏览器的请求的用户,也可能是一个允许的程序。Subject在shiro中是一个接口,接口中定义了很多认证授权的方法,外部程序通过Subject对象进行认证和授权,而Subject对象是通过SecurityManager进行认证和授权!

3.2 SecurityManager

SecurityManager即安全管理器,对全部的Subject对象进行安全管理,它是shiro的核心,通过SecurityManager可以完成对Subject对象的认证授权等系列操作,实质上是使用Authenticator进行认证,使用Authorizer进行授权,使用SessionManager进行会话管理!

3.3 Authenticator

Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供了ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足认证的大部分需求,也可以使用自定义的认证器。

3.4 Authorizer

Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否由此功能的操作权限。

3.5 Realm

Realm即领域,相当于datasource,SecurityManager进行安全认证是需要从Realm获取用户权限数据,比如:用户的身份信息如果放到数据库中,那么就需要从数据库中获取到用户身份信息。

注:不要把Realm认为只是在里面取数据,在Realm还有认证和授权相关的代码!

3.6 SessionManager

SessionManager即会话管理,shiro框架定义了一系列会话管理,它不依赖于web的session,所以shiro可以使用在非web的环境下,也可以将分布式的应用集中在一起管理,此特性可以实现单点登录!

3.7 SessionDao

SessionDao即会话dao,是对session会话操作的一套接口,比如将session存储到数据库,也可以使用jdbc将session存入数据库。

3.8 CacheManager

CacheManager即缓存管理器,将用户权限数据存储到缓存中,可以提高性能。

3.9 Cryptography

Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发,比如提供了常用分散列,盐值计算,加/解密等功能!

4. shiro的认证

4.1 认证

认证就是判断一个用户是否为合法用户的过程,最简单方式就是根据用户输入的用户名和密码,和系统存储层一致不,来判断用户身份是否正确。

4.2 认证的关键对象

  • Subject:主体

访问系统的用户,主体可以是程序,用户等,进行认证的都称为主体。

  • Principal:身份信息

是主体进行身份认证的标识,标识必须具有唯一性,如电话号码,手机号,邮箱地址,一个主体可以有多个身份,但必须有一个主身份!

  • credential:凭证信息

只有主体自己知道的安全信息,如密码,证书等。

4.3 认证流程

4.4 认证的开发

创建项目并引入依赖

<dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.5.3</version></dependency>
</dependencies><build><pluginManagement><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-clean-plugin</artifactId><version>3.1.0</version></plugin></plugins></pluginManagement>
</build>

在resources配置shiro.ini核心配置文件

[users]
admin=123456
lizhipeng=111111
huangyuane=222222

测试认证

/*** 测试认证* @author 隐约雷鸣* @date 2021/1/21 11:11*/
public class TestAuthenticator {public static void main(String[] args) {// 1.创建安全管理器对象DefaultSecurityManager securityManager = new DefaultSecurityManager();// 2.给安全管理器设置realm(本地IniRealm文件)securityManager.setRealm(new IniRealm("classpath:shiro.ini"));// 3.安全工具类设置安全管理器SecurityUtils.setSecurityManager(securityManager);// 4.获取主体对象Subject subject = SecurityUtils.getSubject();// 5.创建令牌UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");// 6.用户认证try {subject.login(token);System.out.println("认证状态:" + subject.isAuthenticated());} catch (UnknownAccountException e) {e.printStackTrace();System.out.println("认证失败:用户名不存在!");} catch (IncorrectCredentialsException e) {e.printStackTrace();System.out.println("认证失败:密码错误!");}}
}

4.5 自定义Realm的开发

使用本地shiro.ini开发,使用的realm就是我们本地提供的,而实际开发中,realm的数据往往是我们的数据库中读取!

1.shiro提供的Realm

2.Realm的实现类中认证是使用SimpleAccountRealm

SimpleAccountRealm 部分源码中有两个方法,分别是

认证:doGetAuthenticationInfo(AuthenticationToken token)

授权:doGetAuthorizationInfo(PrincipalCollection principals)

// 认证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);}}return account;}// 授权protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String username = this.getUsername(principals);this.USERS_LOCK.readLock().lock();AuthorizationInfo var3;try {var3 = (AuthorizationInfo)this.users.get(username);} finally {this.USERS_LOCK.readLock().unlock();}return var3;}

3.自定义Realm

public class CustomerRealm extends AuthorizingRealm {// 认证@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}// 授权@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// 在token中获取身份信息String principal = (String) token.getPrincipal();// 判断获取的身份信息是否与数据库一致,这里暂时使用假数据代替if ("admin".equals(principal)) {/*** 如果用户名通过,返回数据库中用户信息* principal:用户名* credentials:密码* realmName:当前Realm名字*/SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, "123456", this.getName());return authenticationInfo;}return null;}
}

4.使用自定义Realm进行认证

public class TestCustomerRealmAuthenticator {public static void main(String[] args) {// 创建安全管理器对象DefaultSecurityManager securityManager = new DefaultSecurityManager();// 给安全管理设置RealmsecurityManager.setRealm(new CustomerRealm());// 安全工具类设置安全管理器SecurityUtils.setSecurityManager(securityManager);// 获取主体Subject subject = SecurityUtils.getSubject();// 设置tokenUsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");try {subject.login(token);System.out.println(subject.isAuthenticated());} catch (AuthenticationException e) {e.printStackTrace();}}
}

4.6 对密码进行加盐和Hash

1.测试加密,加盐,加散列

public class TestShiroMD5 {public static void main(String[] args) {// 不加盐和散列次数Md5Hash md5Hash1 = new Md5Hash("123456");System.out.println(md5Hash1.toHex());// 加盐,不加散列次数Md5Hash md5Hash2 = new Md5Hash("123456", "X*02");System.out.println(md5Hash2.toHex());// 加盐,加散列次数Md5Hash md5Hash3 = new Md5Hash("123456", "X*02", 1024);System.out.println(md5Hash3.toHex());}
}

自定义Realm,加入MD5+盐+hash

public class CustomerMd5Realm extends AuthorizingRealm {// 认证@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}// 授权@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// 获取身份信息String principal = (String) token.getPrincipal();if ("admin".equals(principal)) {// md5// return new SimpleAuthenticationInfo(principal, "e10adc3949ba59abbe56e057f20f883e", this.getName());// md5 + 盐// return new SimpleAuthenticationInfo(principal, "6c772d52ae59f2cedacf82d0eb60a9ff", ByteSource.Util.bytes("X*02"), this.getName());// md5 + 盐 + hashreturn new SimpleAuthenticationInfo(principal, "791113c0ca9bf9cea458f1221d0fb728", ByteSource.Util.bytes("X*02"), this.getName());}return null;}
}

使用自定义Realm进行认证

public class TestCustomerMd5RealmAuthenticator {public static void main(String[] args) {// 创建安全管理器DefaultSecurityManager securityManager = new DefaultSecurityManager();// 设置Realm使用hash凭证匹配器CustomerMd5Realm realm = new CustomerMd5Realm();HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();credentialsMatcher.setHashAlgorithmName("md5");// 适配器设置散列次数credentialsMatcher.setHashIterations(1024);realm.setCredentialsMatcher(credentialsMatcher);// 注入RealmsecurityManager.setRealm(realm);// 安全管理工具设置安全管理器SecurityUtils.setSecurityManager(securityManager);// 获取主体Subject subject = SecurityUtils.getSubject();// 设置tokenUsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");try {subject.login(token);System.out.println("登录成功:" + subject.isAuthenticated());} catch (UnknownAccountException e) {e.printStackTrace();System.out.println("用户名错误");} catch (IncorrectCredentialsException e) {e.printStackTrace();System.out.println("密码错误");}}
}

5. shiro的授权

5.1 授权

授权,即访问控制,控制谁能访问那些资源。

5.2 授权的关键对象

  • Subject:主体

    主体需要访问系统的资源。

  • Resource:资源

    如系统菜单、页面、按钮、类方法、系统商品信息等。

  • Permission: 权限

    规定了主体对资源的操作许可,权限离开了资源没有意义。

5.3 授权的流程

5.3 授权方式

  • 基于角色的访问控制

    以角色为中心进行访问控制

  • 基于资源的访问控制

    以资源为中心的访问控制

5.5 权限字符串

权限字符串的规则是:资源标识符:操作:资源实例标识符。意思是对哪个资源的那个实例具有什么权限,”:“是分隔符,权限字符串也可以使用“*”通配符。

例子:

用户创建权限: user:create 或 user:create:*
用户修改实例001的权限: user:update:001
用户实例001的所有权限: user:*:001

5.6 shiro中授权的编程实现方式

  • 编程式
Subject subject = SecurityUtils.getSubject();
if (subject.hasRole("admin")) {// 有权限
} else {// 无权限
}
  • 注解式
@RequiresRoles("admin")
public void hello() {// 有权限
}
  • 标签式
<shiro:hasRole name="admin"><!-- 有权限 -->
</shiro:hasRole>
注意:Thymeleaf中使用shiro需要额外集成

5.7 开发授权

自定义Realm, 给用户设置权限

public class CustomerMd5Realm extends AuthorizingRealm {// 授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// 获取主身份信息String primaryPrincipal = (String) principals.getPrimaryPrincipal();System.out.println("身份信息:" + primaryPrincipal);// 根据身份信息获取当前用户的角色信息以及权限信息SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();// 将数据库查到的用户信息赋值给权限对象authorizationInfo.addRole("user");authorizationInfo.addRole("super");// 将数据库查到的用户信息赋值给权限对象authorizationInfo.addStringPermission("user:*:01");authorizationInfo.addStringPermission("product:update");return authorizationInfo;}// 认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// 获取身份信息String principal = (String) token.getPrincipal();if ("admin".equals(principal)) {// md5// return new SimpleAuthenticationInfo(principal, "e10adc3949ba59abbe56e057f20f883e", this.getName());// md5 + 盐// return new SimpleAuthenticationInfo(principal, "6c772d52ae59f2cedacf82d0eb60a9ff", ByteSource.Util.bytes("X*02"), this.getName());// md5 + 盐 + hashreturn new SimpleAuthenticationInfo(principal, "791113c0ca9bf9cea458f1221d0fb728", ByteSource.Util.bytes("X*02"), this.getName());}return null;}
}

使用自定义Realm进行认证授权

public class TestCustomerMd5RealmAuthenticator {public static void main(String[] args) {// 创建安全管理器DefaultSecurityManager securityManager = new DefaultSecurityManager();// 设置Realm使用hash凭证匹配器CustomerMd5Realm realm = new CustomerMd5Realm();HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();credentialsMatcher.setHashAlgorithmName("md5");// 适配器设置散列次数credentialsMatcher.setHashIterations(1024);realm.setCredentialsMatcher(credentialsMatcher);// 注入RealmsecurityManager.setRealm(realm);// 安全管理工具设置安全管理器SecurityUtils.setSecurityManager(securityManager);// 获取主体Subject subject = SecurityUtils.getSubject();// 设置tokenUsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");try {subject.login(token);System.out.println("登录成功:" + subject.isAuthenticated());} catch (UnknownAccountException e) {e.printStackTrace();System.out.println("用户名错误");} catch (IncorrectCredentialsException e) {e.printStackTrace();System.out.println("密码错误");}// 授权if (subject.isAuthenticated()) {// 基于单个角色的权限控制System.out.println("权限:" + subject.hasRole("user"));// 基于多个角色的权限控制System.out.println("权限:" + subject.hasAllRoles(Arrays.asList("user", "super")));// 是否具有其中一个角色boolean[] booleans = subject.hasRoles(Arrays.asList("user", "admin", "super"));for (boolean aBoolean : booleans) {System.out.println(aBoolean);}System.out.println("================");// 基于权限字符串的访问控制System.out.println("权限:" + subject.isPermitted("user:create:01"));System.out.println("权限:" + subject.isPermitted("product:update:02"));// 分别具有哪些权限boolean[] permitted = subject.isPermitted("user:create:01", "order:create:01");for (boolean b : permitted) {System.out.println(b);}// 同时具有哪些权限boolean permittedAll = subject.isPermittedAll("user:*:01", "product:update");System.out.println(permittedAll);}}
}

6. SpringBoot整合shiro

整合思路

6.1 项目准备

导入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
<!--jsp解析依赖-->
<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>

配置application.yml

server:port: 8888servlet:context-path: /shiro
spring:application:name: shiromvc:view:prefix: /suffix: .jsp

在webapp下配置jsp页面

<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>index.jsp</title>
</head>
<body><h1>系统管理</h1><ul><li>用户管理</li><li>商品管理</li><li>订单管理</li><li>物流管理</li></ul>
</body>
</html>
<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>login.jsp</title>
</head>
<body><h1>登录页面</h1>
</body>
</html>

启动类

@SpringBootApplication
public class SpringbootJspShiroApplication {public static void main(String[] args) {SpringApplication.run(SpringbootJspShiroApplication.class, args);}}

6.2 整合shiro

导入依赖

<!--springboot整合shiro依赖-->
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-starter</artifactId><version>1.5.3</version>
</dependency>

创建shiro配置类

@Configuration
public class ShiroConfig {/*** 1.创建ShiroFilter** @param defaultWebSecurityManager* @return*/@Beanpublic ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 设置安全管理器shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);// 配置系统受限资源// 配置系统公共资源Map<String, String> map = new HashMap<>();// authc:请求这个资源需要认证和授权 anon:请求这个资源不需要认证和授权map.put("/user/login", "anon");map.put("/user/register", "anon");map.put("/**", "authc");// 默认认证界面shiroFilterFactoryBean.setLoginUrl("/login.jsp");shiroFilterFactoryBean.setFilterChainDefinitionMap(map);return shiroFilterFactoryBean;}/*** 2. 创建SecurityManager** @param realm* @return*/@Beanpublic DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("realm") Realm realm) {DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();// 设置RealmdefaultWebSecurityManager.setRealm(realm);return defaultWebSecurityManager;}/*** 3.创建自定义Realm** @return*/@Bean(name = "realm")public Realm getRealm() {CustomerRealm customerRealm = new CustomerRealm();return customerRealm;}}

自定义Realm

/*** 自定义Realm** @author 隐约雷鸣* @date 2021/1/27 09:39*/
public class CustomerRealm extends AuthorizingRealm {/*** 授权** @param principals* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}/*** 认证** @param token* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {return null;}
}

测试运行是否成功,如果没报错则证明整合shiro成功,访问index.jsp页面时会自动跳转login.jsp页面

6.3 shiro常见过滤器

配置缩写 对应的过滤器 功能
身份验证相关的
anon AnonymousFilter 指定url可以匿名访问
authc FormAuthenticationFilter 基于表单的拦截器;如“/**=authc”,如果没有登录会跳到相应的登录页面登录;主要属性:usernameParam:表单提交的用户名参数名( username); passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe); loginUrl:登录页面地址(/login.jsp);successUrl:登录成功后的默认重定向地址; failureKeyAttribute:登录失败后错误信息存储key(shiroLoginFailure)
authcBasic BasicHttpAuthenticationFilter Basic HTTP身份验证拦截器,主要属性: applicationName:弹出登录框显示的信息(application)
logout authc.LogoutFilter 退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/)
user UserFilter 用户拦截器,用户已经身份验证/记住我登录的都可
授权相关的
roles RolesAuthorizationFilter 角色授权拦截器,验证用户是否拥有所有角色;主要属性: loginUrl:登录页面地址(/login.jsp);unauthorizedUrl:未授权后重定向的地址;示例“/admin/**=roles[admin]”
perms PermissionsAuthorizationFilter 权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例“/user/**=perms[“user:create”]”
port PortFilter 端口拦截器,主要属性:port(80):可以通过的端口;示例“/test= port[80]”,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样
rest HttpMethodPermissionFilter rest风格拦截器,自动根据请求方法构建权限字符串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串;示例“/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete”权限字符串进行权限匹配(所有都得匹配,isPermittedAll)
ssl SslFilter SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口(443);其他和port拦截器一样
noSessionCreation NoSessionCreationAuthorizationFilter 需要指定权限才能访问

6.4 登录功能实现

login.jsp

<body><h1>登录页面</h1><form method="post" action="${pageContext.request.contextPath}/user/login" >用户名:<input name="username" type="text"> <br/>密码:  <input name="password" type="password"> <br/><input type="submit" value="登录"></form>
</body>

controller

@Controller
@RequestMapping("user")
public class UserController {/*** 登录** @param username* @param password* @return*/@PostMapping("/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";}
}

自定义Realm简单实现认证

/*** 认证** @param token* @return* @throws AuthenticationException*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String principal = (String) token.getPrincipal();if ("admin".equals(principal)) {return new SimpleAuthenticationInfo(principal, "123456", this.getName());}return null;
}

6.5 注销功能实现

Index.jsp

<a href="${pageContext.request.contextPath}/user/logout">注销</a>

controller

/*** 注销** @return*/
@GetMapping("/logout")
public String logout() {Subject subject = SecurityUtils.getSubject();subject.logout();return "redirect:/login.jsp";
}

6.6 基于数据库实现用户注册功能

创建用户表

CREATE TABLE `t_user` (`id` int NOT NULL AUTO_INCREMENT,`username` varchar(40) DEFAULT NULL,`password` varchar(40) DEFAULT NULL,`salt` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

添加依赖

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.2</version>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.2</version>
</dependency>

yml配置

spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql:///shiro?useUniCode=true&useSSL=true&characterEncoding=utf-8&serverTimezone=UTCusername: rootpassword: 1234567890
mybatis:type-aliases-package: com.lzp.springboot_jsp_shiro.entitymapper-locations: classpath:com/lzp/mapper/*.xml

准备注册页面

<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>register.jsp</title>
</head>
<body><h1>注册页面</h1><form method="post" action="${pageContext.request.contextPath}/user/register">用户名:<input name="username" type="text"> <br/>密码:  <input name="password" type="password"> <br/><input type="submit" value="立即注册"></form>
</body>
</html>

用户实体类

@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class User {private Integer id;private String username;private String password;private String salt;
}

生成随机盐工具类

public class SaltUtils {/*** 生成随机盐** @param n* @return*/public static String getSalt(int n) {char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()".toCharArray();StringBuilder sb = new StringBuilder();for (int i = 0; i < n; i++) {char aChar = chars[new Random().nextInt(chars.length)];sb.append(aChar);}return sb.toString();}public static void main(String[] args) {System.out.println(getSalt(4));}
}

controller

/*** 注册** @param user* @return*/
@PostMapping("/register")
public String register(User user) {try {userService.save(user);return "redirect:/login.jsp";} catch (Exception e) {e.printStackTrace();return "redirect:/register.jsp";}
}

service

public interface UserService {/*** 保存用户** @param user*/void save(User user);
}
@Service
@Transactional
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;/*** 保存用户* @param user*/@Overridepublic void save(User user) {// 生成随机盐String salt = SaltUtils.getSalt(8);user.setSalt(salt);// 明文密码进行md5 + 盐 + hashMd5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);user.setPassword(md5Hash.toHex());userMapper.save(user);}
}

mapper

@Mapper
@Repository
public interface UserMapper {/*** 保存用户*/void save(User user);
}
<mapper namespace="com.lzp.springboot_jsp_shiro.mapper.UserMapper"><insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id">insert into t_user values (#{id}, #{username}, #{password}, #{salt});</insert>
</mapper>

6.7 基于数据库重新实现用户认证功能

自定义Realm修改认证逻辑

/*** 认证** @param token* @return* @throws AuthenticationException*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String principal = (String) token.getPrincipal();User user = userService.findByUserName(principal);if (user != null) {return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), this.getName());}return null;
}

service

/*** 根据用户名查询** @param principal*/
User findByUserName(String principal);
/*** 根据用户名查询** @param username*/
@Override
public User findByUserName(String username) {return userMapper.findByUserName(username);
}

mapper

/*** 根据用户名查询** @param username*/
User findByUserName(String username);
<select id="findByUserName" parameterType="String" resultType="User">select * from t_user where username = #{username};
</select>

ShiroConfig修改凭证匹配器,适用于MD5 + 盐 + Hash

@Bean(name = "realm")
public Realm getRealm() {CustomerRealm customerRealm = new CustomerRealm();// 修改凭证校验匹配器HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();// 设置加密算法为MD5credentialsMatcher.setHashAlgorithmName("MD5");// 设置hash散列次数credentialsMatcher.setHashIterations(1024);customerRealm.setCredentialsMatcher(credentialsMatcher);return customerRealm;
}

6.8 授权开发

1.前端页面的授权(基于角色的访问控制)

自定义Realm设置用户对应角色权限

/*** 授权** @param principals* @return*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// 获取身份信息String primaryPrincipal = (String) principals.getPrimaryPrincipal();// 获取角色信息if ("admin".equals(primaryPrincipal)) {SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();// 给admin用户添加admin角色权限authorizationInfo.addRole("admin");return authorizationInfo;}return null;
}

jsp页面添加标签控制访问权限

<body><h1>系统管理</h1><a href="${pageContext.request.contextPath}/user/logout">注销</a><ul><!-- 用户管理只允许user,admin用户看到 --><shiro:hasAnyRoles name="user,admin"><li><a href="">用户管理</li></shiro:hasAnyRoles><shiro:hasRole name="admin"><li><a href="">商品管理</li><li><a href="">订单管理</li><li><a href="">物流管理</li></shiro:hasRole></ul>
</body>

2.前端页面的授权(基于权限字符串的访问控制)

自定义Realm设置用户角色的权限字符串

/*** 授权** @param principals* @return*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// 获取身份信息String primaryPrincipal = (String) principals.getPrimaryPrincipal();// 获取角色信息if ("admin".equals(primaryPrincipal)) {SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();// 基于角色的权限管理:给admin用户添加admin角色权限authorizationInfo.addRole("user");// 基于权限字符串的权限控制:给用户角色添加用户管理的所有权限authorizationInfo.addStringPermission("user:*:*");return authorizationInfo;}return null;
}

jsp页面添加标签控制访问权限

<body><h1>系统管理</h1><a href="${pageContext.request.contextPath}/user/logout">注销</a><ul><shiro:hasAnyRoles name="user,admin"><li><a href="">用户管理</a><ul><shiro:hasPermission name="user:create:*"><li><a href="">添加用户</a></li></shiro:hasPermission><shiro:hasPermission name="user:update:*"><li><a href="">修改用户</a></li></shiro:hasPermission><shiro:hasPermission name="user:delete:*"><li><a href="">删除用户</a></li></shiro:hasPermission><shiro:hasPermission name="user:select:*"><li><a href="">查询用户</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></shiro:hasRole></ul>
</body>

3.后端的授权(基于角色的访问控制)

controller

@Controller
@RequestMapping("order")
public class OrderController {@GetMapping("/create")public String createOrder() {Subject subject = SecurityUtils.getSubject();if (subject.hasRole("admin")) {System.out.println("保存订单");} else {System.out.println("无权访问");}return "redirect:/index.jsp";}
}

自定义Realm设置用户角色为admin

/*** 授权** @param principals* @return*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// 获取身份信息String primaryPrincipal = (String) principals.getPrimaryPrincipal();// 获取角色信息if ("admin".equals(primaryPrincipal)) {SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();// 基于角色的权限管理:给admin用户添加admin角色权限authorizationInfo.addRole("admin");// 基于权限字符串的权限控制:给用户角色添加用户管理的所有权限authorizationInfo.addStringPermission("user:*:*");return authorizationInfo;}return null;
}

注解方式

@GetMapping("/update")
// @RequiresRoles("admin") // 要求admin角色才能访问
@RequiresRoles(value = {"admin", "user"}) // 同时有admin和user角色才能访问
public String updateOrder() {System.out.println("更新成功");return "redirect:/index.jsp";
}

4.后端的授权(基于权限字符串的访问控制)

controller

@GetMapping("/delete")
@RequiresPermissions("user:delete:*") // 要求该用户有user:delete:*权限
public String deleteOrder() {System.out.println("删除成功");return "redirect:/index.jsp";
}

6.9 授权数据持久化

创建数据库表

/*Navicat Premium Data TransferSource Server         : localhostSource Server Type    : MySQLSource Server Version : 80020Source Host           : localhost:3306Source Schema         : shiroTarget Server Type    : MySQLTarget Server Version : 80020File Encoding         : 65001Date: 31/01/2021 22:09:41
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for t_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_perms`;
CREATE TABLE `t_perms` (`id` int NOT NULL AUTO_INCREMENT,`name` varchar(80) DEFAULT NULL,`url` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (`id` int NOT NULL AUTO_INCREMENT,`name` varchar(60) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for t_role_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_role_perms`;
CREATE TABLE `t_role_perms` (`id` int NOT NULL AUTO_INCREMENT,`roleid` int DEFAULT NULL,`permsid` int DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (`id` int NOT NULL AUTO_INCREMENT,`username` varchar(40) DEFAULT NULL,`password` varchar(40) DEFAULT NULL,`salt` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role` (`id` int NOT NULL AUTO_INCREMENT,`userid` int DEFAULT NULL,`roleid` int DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;SET FOREIGN_KEY_CHECKS = 1;

创建对应实体类并建立对应关系

@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class User {private Integer id;private String username;private String password;private String salt;// 定义角色集合private List<Role> roles;
}
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class Role {private Integer id;private String name;// 定义权限集合private List<Perms> permsList;
}
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class Perms {private Integer id;private String name;private String url;
}

6.10 基于数据库重新实现授权流程

自定义Realm修改授权流程

/*** 授权** @param principals* @return*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// 获取身份信息String primaryPrincipal = (String) principals.getPrimaryPrincipal();// 获取角色信息User user = userService.findRolesByUserName(primaryPrincipal);if (!CollectionUtils.isEmpty(user.getRoles())) {SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();user.getRoles().forEach(role -> {authorizationInfo.addRole(role.getName());// 根据角色id设置对应的权限信息List<Perms> permsList = userService.findPermsListByRoleId(role.getId());if (!CollectionUtils.isEmpty(permsList)) {permsList.forEach(perms -> {authorizationInfo.addStringPermission(perms.getName());});}});return authorizationInfo;}return null;
}

service

/*** 根据用户名获取所有角色信息** @param primaryPrincipal* @return*/
User findRolesByUserName(String primaryPrincipal);/*** 根据角色id查询所有权限信息** @param id* @return*/
List<Perms> findPermsListByRoleId(Integer id);
/*** 根据用户名获取所有角色信息** @param username* @return*/
@Override
public User findRolesByUserName(String username) {return userMapper.findRolesByUserName(username);
}/*** 根据角色id查询所有权限信息** @param id* @return*/
@Override
public List<Perms> findPermsListByRoleId(Integer id) {return userMapper.findPermsListByRoleId(id);
}

mapper

/*** 根据用户名获取所有角色信息** @param username* @return*/
User findRolesByUserName(String username);/*** 根据角色id查询所有权限信息** @param id* @return*/
List<Perms> findPermsListByRoleId(Integer id);
<resultMap id="userMap" type="User"><id column="uid" property="id"></id><result column="username" property="username"></result><!-- 角色信息 --><collection property="roles" javaType="List" ofType="Role"><id column="rid" property="id"></id><result column="rname" property="name"></result></collection>
</resultMap><select id="findRolesByUserName" parameterType="String" resultMap="userMap">SELECT u.id uid, u.username, r.id rid, r.`name` rname FROM t_user uLEFT JOIN t_user_role urON u.id =  ur.useridLEFT JOIN t_role rON ur.roleid = r.idWHERE u.username = #{username}
</select><select id="findPermsListByRoleId" parameterType="Integer" resultType="Perms">SELECT r.`name` rname, p.id, p.name, p.url FROM t_role rLEFT JOIN t_role_perms rpON r.id = rp.roleidLEFT JOIN t_perms pON rp.permsid = p.idWHERE r.id = #{id}
</select>

7. 缓存管理器

7.1 使用默认EhCache实现缓存

引入依赖

<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.5.3</version>
</dependency>

ShiroConfig中配置缓存管理器

/*** 3.创建自定义Realm** @return*/
@Bean(name = "realm")
public Realm getRealm() {CustomerRealm customerRealm = new CustomerRealm();// 修改凭证校验匹配器HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();// 设置加密算法为MD5credentialsMatcher.setHashAlgorithmName("MD5");// 设置hash散列次数credentialsMatcher.setHashIterations(1024);customerRealm.setCredentialsMatcher(credentialsMatcher);// 开启缓存管理器customerRealm.setCacheManager(new EhCacheManager());customerRealm.setCachingEnabled(true);customerRealm.setAuthenticationCachingEnabled(true);customerRealm.setAuthenticationCacheName("authenticationCache");customerRealm.setAuthorizationCachingEnabled(true);customerRealm.setAuthorizationCacheName("authorizationCache");return customerRealm;
}

7.2 使用Redis实现缓存

引入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

yml新增redis配置

spring:redis:port: 6379host: localhostdatabase: 0

自定义RedisCacheManager实现CacheManage

public class RedisCacheManager implements CacheManager {/*** @param cacheName 认证或者是授权缓存的统一名称* @param <K>* @param <V>* @return* @throws CacheException*/@Overridepublic <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {System.out.println("cacheName:" + cacheName);return new RedisCache<K, V>(cacheName);}
}

自定义RedisCache实现Cache

public class RedisCache<k, v> implements Cache<k, v> {private String cacheName;public RedisCache() {}public RedisCache(String cacheName) {this.cacheName = cacheName;}@Overridepublic v get(k k) throws CacheException {System.out.println("get:key: " + k);return (v) getRedisTemplate().opsForHash().get(this.cacheName, k.toString());}@Overridepublic v put(k k, v v) throws CacheException {System.out.println("put:key: " + k);System.out.println("put:value: " + v);getRedisTemplate().opsForHash().put(this.cacheName, k.toString(), v);return null;}@Overridepublic v remove(k k) throws CacheException {return (v) getRedisTemplate().opsForHash().delete(this.cacheName, k.toString());}@Overridepublic void clear() throws CacheException {getRedisTemplate().delete(this.cacheName);}@Overridepublic int size() {return getRedisTemplate().opsForHash().size(this.cacheName).intValue();}@Overridepublic Set<k> keys() {return getRedisTemplate().opsForHash().keys(this.cacheName);}@Overridepublic Collection<v> values() {return getRedisTemplate().opsForHash().values(this.cacheName);}/*** 获取RedisTemplate** @return*/private RedisTemplate getRedisTemplate() {RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());return redisTemplate;}
}

ApplicationContextUtils工具类,因为无法自动注入redisTemplate

@Component
public class ApplicationContextUtils implements ApplicationContextAware {private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}//根据bean名称从工厂中获取bean对象public static Object getBean(String name){return  applicationContext.getBean(name);}
}

自定义Salt的实现,实现序列化接口,提供无参构造,不然会报错反序列化异常,其它实体类也需要实现序列化接口

public class MyByteSource implements ByteSource, Serializable {private byte[] bytes;private String cachedHex;private String cachedBase64;public MyByteSource() {}public MyByteSource(byte[] bytes) {this.bytes = bytes;}public MyByteSource(char[] chars) {this.bytes = CodecSupport.toBytes(chars);}public MyByteSource(String string) {this.bytes = CodecSupport.toBytes(string);}public MyByteSource(ByteSource source) {this.bytes = source.getBytes();}public MyByteSource(File file) {this.bytes = (new MyByteSource.BytesHelper()).getBytes(file);}public MyByteSource(InputStream stream) {this.bytes = (new MyByteSource.BytesHelper()).getBytes(stream);}public static boolean isCompatible(Object o) {return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;}public byte[] getBytes() {return this.bytes;}public boolean isEmpty() {return this.bytes == null || this.bytes.length == 0;}public String toHex() {if (this.cachedHex == null) {this.cachedHex = Hex.encodeToString(this.getBytes());}return this.cachedHex;}public String toBase64() {if (this.cachedBase64 == null) {this.cachedBase64 = Base64.encodeToString(this.getBytes());}return this.cachedBase64;}public String toString() {return this.toBase64();}public int hashCode() {return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;}public boolean equals(Object o) {if (o == this) {return true;} else if (o instanceof ByteSource) {ByteSource bs = (ByteSource)o;return Arrays.equals(this.getBytes(), bs.getBytes());} else {return false;}}private static final class BytesHelper extends CodecSupport {private BytesHelper() {}public byte[] getBytes(File file) {return this.toBytes(file);}public byte[] getBytes(InputStream stream) {return this.toBytes(stream);}}
}

CustomerRealm认证时使用自定义的salt实现

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String principal = (String) token.getPrincipal();User user = userService.findByUserName(principal);if (user != null) {return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), new MyByteSource(user.getSalt()), this.getName());}return null;
}

修改ShiroConfig,使用RedisCacheManager实现

@Bean(name = "realm")
public Realm getRealm() {CustomerRealm customerRealm = new CustomerRealm();// 修改凭证校验匹配器HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();// 设置加密算法为MD5credentialsMatcher.setHashAlgorithmName("MD5");// 设置hash散列次数credentialsMatcher.setHashIterations(1024);customerRealm.setCredentialsMatcher(credentialsMatcher);// 开启缓存管理器customerRealm.setCacheManager(new RedisCacheManager());customerRealm.setCachingEnabled(true);customerRealm.setAuthenticationCachingEnabled(true);customerRealm.setAuthenticationCacheName("authenticationCache");customerRealm.setAuthorizationCachingEnabled(true);customerRealm.setAuthorizationCacheName("authorizationCache");return customerRealm;
}

测试登录

登录成功后,会在Redis中存入authenticationCache和com.lzp.springboot_jsp_shiro.shiro.realms.CustomerRealm.authorizationCache这两个key

image-20210203144008837

7.3 验证码登录

login.jsp

<body><h1>登录页面</h1><form method="post" action="${pageContext.request.contextPath}/user/login" >用户名:<input name="username" type="text"> <br/>密码:  <input name="password" type="password"> <br/>验证码:<input type="text" name="cdoe"><img src="${pageContext.request.contextPath}/user/getImage" alt=""><br><input type="submit" value="登录"></form>
</body>

验证码工具类

public class VerifyCodeUtils {//使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符public static final String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";private static Random random = new Random();/*** 使用系统默认字符源生成验证码** @param verifySize 验证码长度* @return*/public static String generateVerifyCode(int verifySize) {return generateVerifyCode(verifySize, VERIFY_CODES);}/*** 使用指定源生成验证码** @param verifySize 验证码长度* @param sources    验证码字符源* @return*/public static String generateVerifyCode(int verifySize, String sources) {if (sources == null || sources.length() == 0) {sources = VERIFY_CODES;}int codesLen = sources.length();Random rand = new Random(System.currentTimeMillis());StringBuilder verifyCode = new StringBuilder(verifySize);for (int i = 0; i < verifySize; i++) {verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1)));}return verifyCode.toString();}/*** 生成随机验证码文件,并返回验证码值** @param w* @param h* @param outputFile* @param verifySize* @return* @throws IOException*/public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException {String verifyCode = generateVerifyCode(verifySize);outputImage(w, h, outputFile, verifyCode);return verifyCode;}/*** 输出随机验证码图片流,并返回验证码值** @param w* @param h* @param os* @param verifySize* @return* @throws IOException*/public static String outputVerifyImage(int w, int h, OutputStream os, int verifySize) throws IOException {String verifyCode = generateVerifyCode(verifySize);outputImage(w, h, os, verifyCode);return verifyCode;}/*** 生成指定验证码图像文件** @param w* @param h* @param outputFile* @param code* @throws IOException*/public static void outputImage(int w, int h, File outputFile, String code) throws IOException {if (outputFile == null) {return;}File dir = outputFile.getParentFile();if (!dir.exists()) {dir.mkdirs();}try {outputFile.createNewFile();FileOutputStream fos = new FileOutputStream(outputFile);outputImage(w, h, fos, code);fos.close();} catch (IOException e) {throw e;}}/*** 输出指定验证码图片流** @param w* @param h* @param os* @param code* @throws IOException*/public static void outputImage(int w, int h, OutputStream os, String code) throws IOException {int verifySize = code.length();BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);Random rand = new Random();Graphics2D g2 = image.createGraphics();g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);Color[] colors = new Color[5];Color[] colorSpaces = new Color[]{Color.WHITE, Color.CYAN,Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,Color.PINK, Color.YELLOW};float[] fractions = new float[colors.length];for (int i = 0; i < colors.length; i++) {colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];fractions[i] = rand.nextFloat();}Arrays.sort(fractions);g2.setColor(Color.GRAY);// 设置边框色g2.fillRect(0, 0, w, h);Color c = getRandColor(200, 250);g2.setColor(c);// 设置背景色g2.fillRect(0, 2, w, h - 4);//绘制干扰线Random random = new Random();g2.setColor(getRandColor(160, 200));// 设置线条的颜色for (int i = 0; i < 20; i++) {int x = random.nextInt(w - 1);int y = random.nextInt(h - 1);int xl = random.nextInt(6) + 1;int yl = random.nextInt(12) + 1;g2.drawLine(x, y, x + xl + 40, y + yl + 20);}// 添加噪点float yawpRate = 0.05f;// 噪声率int area = (int) (yawpRate * w * h);for (int i = 0; i < area; i++) {int x = random.nextInt(w);int y = random.nextInt(h);int rgb = getRandomIntColor();image.setRGB(x, y, rgb);}shear(g2, w, h, c);// 使图片扭曲g2.setColor(getRandColor(100, 160));int fontSize = h - 4;Font font = new Font("Algerian", Font.ITALIC, fontSize);g2.setFont(font);char[] chars = code.toCharArray();for (int i = 0; i < verifySize; i++) {AffineTransform affine = new AffineTransform();affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize / 2, h / 2);g2.setTransform(affine);g2.drawChars(chars, i, 1, ((w - 10) / verifySize) * i + 5, h / 2 + fontSize / 2 - 10);}g2.dispose();ImageIO.write(image, "jpg", os);}private static Color getRandColor(int fc, int bc) {if (fc > 255)fc = 255;if (bc > 255)bc = 255;int r = fc + random.nextInt(bc - fc);int g = fc + random.nextInt(bc - fc);int b = fc + random.nextInt(bc - fc);return new Color(r, g, b);}private static int getRandomIntColor() {int[] rgb = getRandomRgb();int color = 0;for (int c : rgb) {color = color << 8;color = color | c;}return color;}private static int[] getRandomRgb() {int[] rgb = new int[3];for (int i = 0; i < 3; i++) {rgb[i] = random.nextInt(255);}return rgb;}private static void shear(Graphics g, int w1, int h1, Color color) {shearX(g, w1, h1, color);shearY(g, w1, h1, color);}private static void shearX(Graphics g, int w1, int h1, Color color) {int period = random.nextInt(2);boolean borderGap = true;int frames = 1;int phase = random.nextInt(2);for (int i = 0; i < h1; i++) {double d = (double) (period >> 1)* Math.sin((double) i / (double) period+ (6.2831853071795862D * (double) phase)/ (double) frames);g.copyArea(0, i, w1, 1, (int) d, 0);if (borderGap) {g.setColor(color);g.drawLine((int) d, i, 0, i);g.drawLine((int) d + w1, i, w1, i);}}}private static void shearY(Graphics g, int w1, int h1, Color color) {int period = random.nextInt(40) + 10; // 50;boolean borderGap = true;int frames = 20;int phase = 7;for (int i = 0; i < w1; i++) {double d = (double) (period >> 1)* Math.sin((double) i / (double) period+ (6.2831853071795862D * (double) phase)/ (double) frames);g.copyArea(i, 0, 1, h1, 0, (int) d);if (borderGap) {g.setColor(color);g.drawLine(i, (int) d, i, 0);g.drawLine(i, (int) d + h1, i, h1);}}}public static void main(String[] args) throws IOException {File dir = new File("D:/upload/verifyCode");int w = 200, h = 80;for (int i = 0; i < 50; i++) {String verifyCode = generateVerifyCode(4);File file = new File(dir, verifyCode + ".jpg");outputImage(w, h, file, verifyCode);}}
}

controller

/*** 登录** @param username* @param password* @return*/
@PostMapping("/login")
public String login(String username, String password, String code, HttpSession session) {try {// 校验验证码String codes = (String) session.getAttribute("code");if (codes.equalsIgnoreCase(code)) {// 获取主体Subject subject = SecurityUtils.getSubject();subject.login(new UsernamePasswordToken(username, password));return "redirect:/index.jsp";} else {throw new RuntimeException("验证码错误!");}} catch (UnknownAccountException e) {e.printStackTrace();System.out.println("用户名错误!");} catch (IncorrectCredentialsException e) {e.printStackTrace();System.out.println("密码错误!");} catch (Exception e) {e.printStackTrace();System.out.println(e.getMessage());}return "redirect:/login.jsp";
}/*** 验证码** @param session* @param response*/
@GetMapping("/getImage")
public void getImage(HttpSession session, HttpServletResponse response) throws IOException {// 生成验证码存入sessionString code = VerifyCodeUtils.generateVerifyCode(4);session.setAttribute("code", code);ServletOutputStream outputStream = response.getOutputStream();// 验证码存入图片response.setContentType("image/png");VerifyCodeUtils.outputImage(200, 30, outputStream, code);
}

Shiro实战教程笔记相关推荐

  1. Shiro 实战教程

    Shiro 实战教程 1.权限的管理 1.1 什么是权限管理 ​ 基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制 ...

  2. OpenCV4经典案例实战教程 笔记

    OpenCV4经典案例实战教程 笔记 这几天在看OpenCV4经典的案例实战教程,这里记录一下学习的过程. 案例一 刀片1的缺陷检测 这里的目的是检测出有缺陷的刀片,如下图. 先总结一下思路,这里首先 ...

  3. 尚硅谷Docker实战教程-笔记02【安装docker、镜像加速器配置】

    尚硅谷大数据技术-教程-学习路线-笔记汇总表[课程资料下载] 视频地址:尚硅谷Docker实战教程(docker教程天花板)_哔哩哔哩_bilibili 尚硅谷Docker实战教程-笔记01[理念简介 ...

  4. Shiro 实战教程(全)

    目录 1.权限的管理 1.1 什么是权限管理 1.2 什么是身份认证 1.3 什么是授权 2.什么是shiro 3.shiro的核心架构 3.1 Subject 3.2 SecurityManager ...

  5. 一文总结 Shiro 实战教程

    ✅作者简介:2022年博客新星 第八.热爱国学的Java后端开发者,修心和技术同步精进.

  6. 数据分析中的常用数学模型实战教程笔记(下)

    文章目录 SVM模型 代码操作 手写体字母识别 用最佳参数做预测 使用默认参数做预测 森林火灾可能性预测 Kmeans-K均值聚类模型 随机一个三组二元正态分布随机数 拐点法 轮廓系数法 函数代码 花 ...

  7. Kubernetes(k8s)入门到实战教程笔记总结

    一.k8s概念和架构 2.1 k8s概述 k8s 是 2014 年 Google 开源的一款容器化集群管理系统,使用 golang 实现 可使用 k8s 进行容器化应用部署,使部署更加方便 使用 k8 ...

  8. OpenCV4 图像处理与视频分析实战教程 笔记

    14. 图像直方图的比较 比较巴氏距离和余弦相似度 void hist_compare() {Mat src1 = imread("D:/images/hist_01.jpg"); ...

  9. HarmonyOS2.0 华为鸿蒙开发学习实战教程

    据统计,鸿蒙系统升级用户2周破1800万,1个月突破3000万.升级速度完全超出市场预期,很显然,华为严重低估了消费者的支持热情.随即,华为鸿蒙目标多次升级,从2亿到4亿,后年目标12.3亿. 而且, ...

  10. 《黑马程序员2023新版黑马程序员大数据入门到实战教程,大数据开发必会的Hadoop、Hive,云平台实战项目》学习笔记总目录

    本文是对<黑马程序员新版大数据入门到实战教程>所有知识点的笔记进行总结分类. 学习视频:黑马程序员新版大数据 学习时总结的学习笔记以及思维导图会在后续更新,请敬请期待. 前言:配置三台虚拟 ...

最新文章

  1. 机器学习各领域必读经典综述
  2. pro c中varchar类型变量的arr函数是做什么用
  3. 有没有一种让人很爽的学习方法?
  4. AutoCAD 2010建筑土木制图高清实例视频教程
  5. 逆向生成的Dimac.JMail工程及测试项目
  6. mysql 5.7.11 x64_mysql 5.7.11 winx64安装配置教程
  7. stm32F103驱动ADS1115程序-4通道可用-ALERT中断可用-ADC芯片,应该是最全了
  8. Redis在游戏服务器中的应用
  9. 联想r720游戏模式不见了
  10. dellt130服务器做系统,戴尔Dell R330;T130安装系统后键盘鼠标不能使用
  11. SpringBoot整合Elasticsearch,应届毕业生java面试准备材料
  12. 拓嘉辰丰:拼多多订单退款流程有哪些?
  13. MATLAB数据转化num,str,cell,char
  14. 微信小程序项目实例——心情记事本
  15. VS2019安装(含免费安装包)
  16. python winapi_python---win32gui、win32con、win32api:winAPI操作
  17. ksweb如何安装php5.6_KSWEB - server + PHP + MySQL
  18. Idea导出可运行jar包及运行方法
  19. aircrack-ng渗透WPA加密
  20. 美容院预约系统软件开发

热门文章

  1. java实现生命游戏
  2. Matlab下的整数规划(CVX)
  3. dplayer安装php_Dplayer播放器集成p2p加速源码分享
  4. 条形码技术应用属于计算机系统的,条形码技术在现代物流系统中的应用
  5. 项目管理的五个过程和九大知识领域
  6. 图像处理之图像质量评价指标SSIM(结构相似性)
  7. udp端口转发 Linux,Linux下利用iptables快速实现UDP/TCP端口转发
  8. dbv连接mysql_mysql数据库版本控制dbv使用_MySQL
  9. SECS Message解析说明
  10. 14 、软件测试-MySQL的基本使用方法-增删改查