SpringBoot11:集成Shiro
Shiro简介
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
- Shiro是一个开源的java安全(权限)框架,它能够实现身份验证、授权、加密和会话管理等功能。
- Shiro是apache旗下的产品,它的官网是: shiro官网: Apache Shiro
- Shiro不仅可以用于javaEE环境,也可以用于javaSE
主要功能
.
- Authentication:身份认证,验证用户是否拥有某个身份。
- Authorization: 权限校验,验证某个已认证的用户是否拥有某个权限。确定“谁”可以访问“什么”
- Session Management:会话管理,管理用户登录后的会话,
- Cryptography:加密,使用密码学加密数据,如加密密码。
- Web Support:Web支持,能够比较轻易地整合到Web环境中。
- Caching:缓存,对用户的数据进行缓存,
- Concurrency:并发,Apache Shiro支持具有并发功能的多线程应用程序,也就是说支持在多线程应用中并发验证
- Testing:测试,提供了测试的支持。
- Run as :允许用户以其他用户的身份来登录。
- Remember me :记住我
三个核心组件:Subject, SecurityManager 和 Realms
Subject
:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。SecurityManager
:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。Realm
: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
整合Shiro-SpringBoot-Thymeleaf-Mybatis
项目结构
..
运行效果
.
.
.
.
.
.
pom.xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--shiro整合spring--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.5.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.22</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.2</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
ShiroConfig.java
package com.godfrey.config; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; /** * description : ShiroConfig * * @author godfrey * @since 2020-06-02 */ @Configuration public class ShiroConfig { //ShiroFilterFactoryBean 第三步 @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全关联器 bean.setSecurityManager(defaultWebSecurityManager); /** * 添加shiro的内置过滤器: * anon: 无需认证就可访问 * authc:必须认证才能访问 * user:必须拥有记住我功能才能访问 * perms: 拥有对某个资源的权限才能访问 * role:拥有某个角色权限才能访问 */ Map<String, String> filterMap = new LinkedHashMap<>(); //授权 filterMap.put("/user/add", "perms[user:add]"); filterMap.put("/user/update", "perms[user:update]"); filterMap.put("/user/*", "authc"); //设置登出 filterMap.put("/logout", "logout"); bean.setFilterChainDefinitionMap(filterMap); //设置登录请求 bean.setLoginUrl("/toLogin"); //设置未授权页面 bean.setUnauthorizedUrl("/noauth"); return bean; } //DefaultWebSecurityManager 第二步 @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //关联Realm securityManager.setRealm(userRealm); return securityManager; } //创建realm对象 ,需要自定义 第一步 @Bean public UserRealm userRealm() { return new UserRealm(); } //ShiroDialect 整合 shiro thymeleaf @Bean public ShiroDialect getShiroDialect() { return new ShiroDialect(); } }
UserRealm.java
package com.godfrey.config; import com.godfrey.pojo.User; import com.godfrey.service.UserService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.session.Session; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; /** * description : 自定义Realm * * @author godfrey * @since 2020-06-02 */ public class UserRealm extends AuthorizingRealm{ @Autowired UserService userService; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了=>授权doGetAuthorizationInfo"); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //获取当前登录的对象 Subject subject = SecurityUtils.getSubject(); User currentUser = (User) subject.getPrincipal(); //设置当前用户的权限 info.addStringPermission(currentUser.getPerms()); return info; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了=>认证doGetAuthenticationInfo"); UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken; //连接数据库,取用户名、密码 User user = userService.queryUserByName(userToken.getUsername()); if (user==null){ return null;//抛出异常 UnknownAccountException } Subject currentSubject = SecurityUtils.getSubject(); Session session = currentSubject.getSession(); session.setAttribute("loginUser",user); //密码认证~shiro做 return new SimpleAuthenticationInfo(user,user.getPwd(),""); } }
MyController.java
package com.godfrey.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; /** * description : MyController * * @author godfrey * @since 2020-06-02 */ @Controller public class MyController { @RequestMapping({"/", "/index"}) public String toIndex(Model model) { model.addAttribute("msg", "hello shiro"); return "index"; } @RequestMapping("/user/add") public String add() { return "user/add"; } @RequestMapping("/user/update") public String update() { return "user/update"; } @RequestMapping("/toLogin") public String toLogin() { return "login"; } @RequestMapping("/login") public String login(String username, String password, Model model) { //获取当前用户 Subject subject = SecurityUtils.getSubject(); //封装用户的登录数据 UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { subject.login(token); return "index"; } catch (UnknownAccountException e) { model.addAttribute("msg", "用户名错误"); return "login"; } catch (IncorrectCredentialsException e) { model.addAttribute("msg", "密码错误"); return "login"; } } @RequestMapping("/noauth") @ResponseBody public String unauthorized() { return "未授权无法访问此页面"; } }
UserMapper.java
package com.godfrey.mapper; import com.godfrey.pojo.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Repository; /** * description : UserMapper * * @author godfrey * @since 2020-06-02 */ @Mapper @Repository public interface UserMapper { User queryUserByName(@Param("name") String name); }
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.godfrey.mapper.UserMapper"> <select id="queryUserByName" parameterType="String" resultType="User"> select * from mybatis.user where name = #{name}; </select> </mapper>
User.java
package com.godfrey.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * description : User实体类 * * @author godfrey * @since 2020-06-02 */ @Data @NoArgsConstructor @AllArgsConstructor public class User { private Integer id; private String name; private String pwd; private String perms; }
UserService.java
package com.godfrey.service; import com.godfrey.pojo.User; /** * description : UserService * * @author godfrey * @since 2020-06-02 */ public interface UserService { User queryUserByName(String name); }
UserServiceImpl.java
package com.godfrey.service.impl; import com.godfrey.mapper.UserMapper; import com.godfrey.pojo.User; import com.godfrey.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * description : UserServiceImpl * * @author godfrey * @since 2020-06-02 */ @Service public class UserServiceImpl implements UserService { @Autowired UserMapper userMapper; @Override public User queryUserByName(String name) { return userMapper.queryUserByName(name); } }
application.yml
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源 #Spring Boot 默认是不注入这些属性值的,需要自己绑定 #druid 数据源专有配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 mybatis: type-aliases-package: com.godfrey.pojo mapper-locations: classpath:com/godfrey/mapper/*Mapper.xml
index.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"> <head> <title>首页</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> </head> <body> <h1>首页</h1> <p th:if="${session.loginUser==null}"> <a th:href="@{/toLogin}">登录</a> </p> <p th:if="${session.loginUser!=null}"> <a th:href="@{/logout}">登出</a> </p> <p th:text="${msg}"></p> <hr> <div shiro:hasPermission="user:add"> <a th:href="@{/user/add}">add</a> </div> <div shiro:hasPermission="user:update"> <a th:href="@{/user/update}">update</a> </div> </body> </html>
login.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>登录</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> </head> <body> <h1>登录</h1> <hr> <p th:text="${msg}" style="color: red"></p> <form th:action="@{/login}"> <p>用户名:<input type="text" name="username"></p> <p>密 码:<input type="password" name="password"></p> <p><input type="submit"></p> </form> </body> </html>
add.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>add</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> </head> <body> <h1>add</h1> </body> </html>
update.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>update</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> </head> <body> <h1>update</h1> </body> </html>