shiro的10分钟快速开始 1.导入依赖
新建一个普通的maven项目,然后new一个hello-shiro(moudle)作为第一个测试项目
导入对应的依赖在pom.xml文件里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <dependencies > <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-core</artifactId > <version > 1.7.1</version > </dependency > <dependency > <groupId > org.slf4j</groupId > <artifactId > jcl-over-slf4j</artifactId > <version > 1.7.21</version > </dependency > <dependency > <groupId > org.slf4j</groupId > <artifactId > slf4j-log4j12</artifactId > <version > 1.7.21</version > </dependency > <dependency > <groupId > log4j</groupId > <artifactId > log4j</artifactId > <version > 1.2.17</version > </dependency > </dependencies >
开启项目检查 在官方的Github目录下下载zip或者直接copy代码
shiro官方Github:https://github.com/apache/shiro
在resources目录下新建一个log4j.properties和shiro.ini文件 log4j.properties具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 log4j.rootLogger =INFO, stdout log4j.appender.stdout =org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout =org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern =%d %p [%c] - %m %n log4j.logger.org.apache =WARN log4j.logger.org.springframework =WARN log4j.logger.org.apache.shiro =INFO log4j.logger.org.apache.shiro.util.ThreadContext =WARN log4j.logger.org.apache.shiro.cache.ehcache.EhCache =WARN
shiro.ini的具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 [users] root = secret, adminguest = guest, guestpresidentskroob = 12345 , presidentdarkhelmet = ludicrousspeed, darklord, schwartzlonestarr = vespa, goodguy, schwartz[roles] admin = *schwartz = lightsaber:*goodguy = winnebago:drive:eagle5
在java目录导入Quickstart.java,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.*;import org.apache.shiro.config.IniSecurityManagerFactory;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.session.Session;import org.apache.shiro.subject.Subject;import org.apache.shiro.util.Factory;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class Quickstart { private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class); public static void main (String[] args) { Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini" ); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject currentUser = SecurityUtils.getSubject(); Session session = currentUser.getSession(); session.setAttribute("someKey" , "aValue" ); String value = (String) session.getAttribute("someKey" ); if (value.equals("aValue" )) { log.info("Retrieved the correct value! [" + value + "]" ); } if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken("lonestarr" , "vespa" ); token.setRememberMe(true ); try { currentUser.login(token); } catch (UnknownAccountException uae) { log.info("There is no user with username of " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { log.info("Password for account " + token.getPrincipal() + " was incorrect!" ); } catch (LockedAccountException lae) { log.info("The account for username " + token.getPrincipal() + " is locked. " + "Please contact your administrator to unlock it." ); } catch (AuthenticationException ae) { } } log.info("User [" + currentUser.getPrincipal() + "] logged in successfully." ); if (currentUser.hasRole("schwartz" )) { log.info("May the Schwartz be with you!" ); } else { log.info("Hello, mere mortal." ); } if (currentUser.isPermitted("lightsaber:wield" )) { log.info("You may use a lightsaber ring. Use it wisely." ); } else { log.info("Sorry, lightsaber rings are for schwartz masters only." ); } if (currentUser.isPermitted("winnebago:drive:eagle5" )) { log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " + "Here are the keys - have fun!" ); } else { log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!" ); } currentUser.logout(); System.exit(0 ); } }
3.开启项目检查
看到能打印出这行信息,说明快如入门成功
springboot整合shiro环境搭建 新建一个moudle叫shiro-springboot,勾选spring web依赖即可
在pom.xml中导入thymeleaf依赖
1 2 3 4 5 6 7 8 9 <dependency > <groupId > org.thymeleaf</groupId > <artifactId > thymeleaf-spring5</artifactId > </dependency > <dependency > <groupId > org.thymeleaf.extras</groupId > <artifactId > thymeleaf-extras-java8time</artifactId > </dependency >
新建一个controller编写一个MyController测试,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;@Controller public class MyController { @RequestMapping({"/index","/"}) public String toIndex (Model model) { model.addAttribute("msg" ,"hello,shiro" ); return "index" ; } }
在templates下新建一个index.html,导入thymeleaf约束,这样可以编写thymeleaf提示
xmlns:th=”http://www.thymeleaf.org “
index.html完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.themeleaf.org" > <head > <meta charset ="UTF-8" > <title > 首页</title > </head > <body > <h1 > 首页</h1 > <p th:text ="${msg}" > </p > </body > </html >
运行项目检查
shiro的三大对象:
Subject:用户
SecurityManager:管理所有用户
Realm:连接数据
pom.xml导入依赖:
1 2 3 4 5 6 <dependency > <groupId > org.apache.shiro</groupId > <artifactId > shiro-spring</artifactId > <version > 1.7.1</version > </dependency >
创建一个config包编写ShiroConfig配置类 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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;@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean (@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(defaultWebSecurityManager); return bean; } @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager (@Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm); return securityManager; } @Bean public UserRealm userRealm () { return new UserRealm(); } }
因为配置涉及到userRealm,这个需要自己自定义,所以在config包下再写一个UserRealm类 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;public class UserRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { System.out.println("执行了授权的=>doGetAuthorizationInfo" ); return null ; } @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了认证的=>doGetAuthenticationInfo" ); return null ; } }
在templates建一个user夹放关于用户的页面:add.html;update:html
回到MyController 添加两个页面跳转的方法
1 2 3 4 5 6 7 8 @RequestMapping("/user/add") public String add () { return "user/add" ; } @RequestMapping("/user/update") public String update () { return "user/update" ; }
回到主页index.html实现跳转
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.themeleaf.org" > <head > <meta charset ="UTF-8" > <title > 首页</title > </head > <body > <h1 > 首页</h1 > <p th:text ="${msg}" > </p > <a th:href ="@{/user/add}" > 添加</a > | <a th:href ="@{/user/update}" > 更新</a > </body > </html >
再次重启项目检查
到此环境搭建完成!
shiro实现登陆拦截 在ShiroConfig添加shiro的内置过滤器
1 2 3 4 5 6 7 8 9 10 11 12 LinkedHashMap<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/user/add" ,"authc" ); filterMap.put("/user/update" ,"authc" ); bean.setFilterChainDefinitionMap(filterMap);
再次运行项目点击add,发现失败,此时拦截已经成功了
因为它跳转的url是login页面,所以还得重写login页面 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 用户登录</title > </head > <body > <h1 > 登录</h1 > <form action ="/toLogin" method ="post" > <p > 用户名: <input type ="text" name ="username" > </p > <p > 密码: <input type ="password" name ="password" > </p > <p > <input type ="submit" > </p > </form > </body > </html >
MyController添加方法
1 2 3 4 @RequestMapping("/toLogin") public String toLogin () { return "login" ; }
配置登录页面
再次运行项目点击add或者update已经跳转到了登录页面,说明已经拦截成功!
shiro实现用户认证 在MyController添加login方法:代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @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" ; } }
在login.html写入信息msg
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.thymeleaf.org" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <h1 > 登录</h1 > <p th:text ="${msg}" style ="color: red" > </p > <form th:action ="@{/login}" method ="post" > <p > 用户名: <input type ="text" name ="username" > </p > <p > 密码: <input type ="password" name ="password" > </p > <p > <input type ="submit" > </p > </form > </body > </html >
启动项目登录测试
在UserRealm代码下修改认证代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken token) throws AuthenticationException {System.out.println("执行了认证的=>doGetAuthenticationInfo" ); String name="root" ; String password="123456" ; UsernamePasswordToken userToken = (UsernamePasswordToken) token; if (!userToken.getUsername().equals(name)){return null ;} return new SimpleAuthenticationInfo("" ,password,"" );}
重新启动项目登录测试
shiro整合mybatis 在pom.xml导入对应的依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > log4j</groupId > <artifactId > log4j</artifactId > <version > 1.2.17</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.2.6</version > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 2.1.4</version > </dependency >
在resource目录下新建application.yaml配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 spring: datasource: username: root password: 123456 url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource 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,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
项目新建数据库连接绑定mybatis的数据库
application.properties文件里配置mybatis的相关设置
1 2 mybatis.type-aliases-package =com.example.pojo mybatis.mapper-locations =classpath:mapper/*xml
由于配置文件里多了别名的扫描和mapper的文件,所以要完整架构,新建一个pojo实体类和mapper的包,如下
pojo实体类为了方便代码简洁,我使用了lombok,在pom导入对应依赖即可
1 2 3 4 <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
实体类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package cn.dzp.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private String pwd; }
编写mapper
1 2 3 4 5 6 7 8 9 10 import com.example.pojo.User;import org.apache.ibatis.annotations.Mapper;import org.springframework.stereotype.Repository;@Repository @Mapper public interface UserMapper { public User queryUserByName (String name) ; }
编写UserMapper.xml
1 2 3 4 5 6 7 8 9 <?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 ="cn.dzp.mapper.UserMapper" > <select id ="queryUserByName" parameterType ="String" resultType ="user" > select * from user where name=#{name} </select > </mapper >
新建service写一个UserService接口和它的实现类
1 2 3 public interface UserService { public User queryUserByName (String name) ; }
UserMapperImpl实现类代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import com.example.mapper.UserMapper;import com.example.pojo.User;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service public class UserMapperImpl implements UserService { @Autowired UserMapper userMapper; @Override public User queryUserByName (String name) { return userMapper.queryUserByName(name); } }
在测试类测试代码可以看到查询成功
这样就可以去改Realm的代码,开始的用户名和密码都是手写伪造的
UserRealm代码修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package cn.dzp.config;import cn.dzp.pojo.User;import cn.dzp.service.UserService;import org.apache.shiro.authc.*;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;import org.springframework.beans.factory.annotation.Autowired;public class UserRealm extends AuthorizingRealm { @Autowired UserService userService; @Override protected AuthorizationInfo doGetAuthorizationInfo (PrincipalCollection principalCollection) { System.out.println("执行了授权的=>doGetAuthorizationInfo" ); return null ; } @Override protected AuthenticationInfo doGetAuthenticationInfo (AuthenticationToken token) throws AuthenticationException { System.out.println("执行了认证的=>doGetAuthenticationInfo" ); UsernamePasswordToken userToken = (UsernamePasswordToken) token; User user = userService.queryUserByName(userToken.getUsername()); if (user==null ){ return null ; } return new SimpleAuthenticationInfo("" ,user.getPwd(),"" ); } }
现在的话登录用户就是数据库中的,启动项目测试可以看到登录成功
shiro实现请求授权 在ShiroConfig添加部分代码,如图:
正常情况下,授权会跳转到未授权的页面,所以才MyController写一个跳转到未授权的页面方法
1 2 3 4 5 @ResponseBody @RequestMapping("/noauth") public String unauthorized () { return "无法访问此页面" ; }
ShiroConfig类修改一下:
重启项目测试,发现已经可以跳转到我们设置的未授权的页面了
怎样添加add页面的授权呢,在UserRealm修改下代码,因为ShiroConfig设定了add页面需要权限,所以要在UserRealm添加权限
但是所有登录的用户都有此权限,所以我打算把数据库的表新增一个权限的字段,添加下权限
记得改下User实体类
UserRealm类
ShiroConfig里添加对update的过滤
开启项目测试
shiro整合thymeleaf 导入对应的依赖
1 2 3 4 5 6 7 <dependency > <groupId > com.github.theborakompanioni</groupId > <artifactId > thymeleaf-extras-shiro</artifactId > <version > 2.0.0</version > ShiroConfig类添加方法
在ShiroConfig里注入bean
1 2 3 4 5 @Bean public ShiroDialect getShiroDialect () {return new ShiroDialect();}
在index.html添加约束
xmlns:shiro=”http://www.pollix.at/thymeleaf/shiro “
实现注销功能
在MyController写跳转注销功能
1 2 3 4 5 6 @RequestMapping("/logout") public String logout () { Subject subject = SecurityUtils.getSubject(); subject.logout(); return "index" ; }
登陆后不显示登录按钮
加入shiro:notAuthenticated
前端修改index.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.themeleaf.org" xmlns:shiro ="http://www.thymeleaf.org/thymeleaf-extras-shiro" > <head > <meta charset ="UTF-8" > <title > 首页</title > </head > <body > <h1 > 首页</h1 > <p th:text ="${msg}" > </p > <div shiro:hasPermission ="user:add" > <a th:href ="@{/user/add}" > 添加</a > </div > <div shiro:hasPermission ="user:update" > <a th:href ="@{/user/update}" > 更新</a > </div > <br > <div shiro:notAuthenticated ="" > <a th:href ="@{/toLogin}" > 登录</a > </div > <br > <div > <a th:href ="@{/logout}" > 注销</a > </div > </body > </html >
运行项目检查