<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--Subject 用户 SecurityManager 管理所有用户 Realm 连接数据--> <!--shiro整合spring的包--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.7.1</version> </dependency> <!--thymeleaf模板--> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.0.12.RELEASE</version> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-java8time</artifactId> <version>3.0.4.RELEASE</version> </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> </dependencies>
配置类:
自定义UserRealm类:
package com.kuang.config; 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; import org.springframework.context.annotation.Configuration; //自定义的UserRealm extends AuthorizationRealm 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; } }
重点:
在写shiroConfig配置类前,我们要了解的原理,shiro架构
SecurityManager底层源码,都是利用工厂的设计模式
原本以前我们是可以通过ini配置文件完成的,代码如下: 1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); 2、得到SecurityManager实例 并绑定给SecurityUtils SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager);
核心配置类:shiroConfig
package com.kuang.config; 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 { //3.shiroFilterFactoryBean @Bean("shiroFilterFactoryBean") public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); return bean; } //2.DefaultWebSecurityManager @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //关联UserRealm securityManager.setRealm(userRealm); return securityManager; } //1.创建realm对象,需要自定义类 @Bean public UserRealm userRealm() { return new UserRealm(); } }
上面紫色的代码,我原本写的是@Bean,后来出现下面的错误
也就是默认的是方法名,也就是 getShiroFilterFactoryBean,这里要指定为 shiroFilterFactoryBean
此时,咱的shiro环境就搭建好啦
index.html:
<!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}"></p> <a th:href="@{/user/add}">add</a><br> <a th:href="@{/user/update}">update</a> </body> </html>
控制层的MyController:
package com.kuang.controller; 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"; } @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"; } }
重点:
shiro的五种内置过滤器:
anon: 无需认证就可以访问
authc: 必须认证了才能访问
user: 必须拥有 记住我 功能才能用
perms: 拥有对某个资源的权限才能访问
roles: 拥有某个角色权限才能访问
配置ShiroConfig
package com.kuang.config; 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.HashMap; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { //3.shiroFilterFactoryBean //@Qualifier代表spring里面的 @Bean("shiroFilterFactoryBean") public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); //添加shiro的内置过滤器 /* anon: 无需认证就可以访问 authc: 必须认证了才能访问 user: 必须拥有 记住我 功能才能用 perms: 拥有对某个资源的权限才能访问 roles: 拥有某个角色权限才能访问 */ //可以看DefaultFilter,这是一个枚举类,定义了很多的拦截器authc,anon等分别有对应的拦截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //filterChainDefinitionMap.put("/user/add","authc"); //filterChainDefinitionMap.put("/user/update","authc"); filterChainDefinitionMap.put("/user/*", "authc"); //代表着前面的url路径,用后面指定的拦截器进行拦截,此处用的通配符 //filterChainDefinitionMap.put("/admin", "roles[admin]"); //admin的url,要用角色是admin的才可以登录,对应的拦截器是RolesAuthorizationFilter //filterChainDefinitionMap.put("/edit", "perms[edit]"); //拥有edit权限的用户才有资格去访问 bean.setFilterChainDefinitionMap(filterChainDefinitionMap); //设置一个拦截器链 //设置登录的请求 bean.setLoginUrl("/toLogin"); return bean; } //2.DefaultWebSecurityManager /* * 注入一个securityManager * 原本以前我们是可以通过ini配置文件完成的,代码如下: * 1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); 2、得到SecurityManager实例 并绑定给SecurityUtils SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); * */ @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { //这个DefaultWebSecurityManager构造函数,会对Subject,realm等进行基本的参数注入 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //关联UserRealm securityManager.setRealm(userRealm);//往SecurityManager中注入Realm,代替原本的默认配置 return securityManager; } //1.创建realm对象,需要自定义类 @Bean public UserRealm userRealm() { return new UserRealm(); } }
前端表单页:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登录</h1> <hr> <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" value="登录"></p> </form> </body> </html>
controller层的 MyController 中添加登录:
@Controller public class MyController { @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);//执行登录的方法,如果没有异常就说明OK啦 return "index"; } catch (UnknownAccountException e) { //用户名不存在 model.addAttribute("msg","用户名错误"); return "login"; } catch (IncorrectCredentialsException e) { //密码不存在 model.addAttribute("msg","密码错误"); return "login"; } } }
注意:
UnknownAccountException:用户不存在,是固定的
IncorrectCredentialsException:密码不存在,是固定的
配置类:UserRealm
package com.kuang.config; import org.apache.shiro.SecurityUtils; 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.apache.shiro.subject.Subject; import org.springframework.context.annotation.Configuration; //自定义的UserRealm extends AuthorizationRealm 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"); //用户名,密码,数据库中取,此处模仿 String name = "root"; String password = "123"; UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken; if (!userToken.getUsername().equals(name)){ return null; // 抛出异常,咱自己定义的 UnknownAccountException } //密码认证,shiro做 return new SimpleAuthenticationInfo("",password,""); } }
注意:
这里面的密码认证是shiro做的
创建user表
pojo类:
package com.kuang.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String username; private String password; private String perms; }
在 ShiroConfig 配置类中,设置授权和和未授权配置
package com.kuang.config; 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.HashMap; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { //3.shiroFilterFactoryBean //@Qualifier代表spring里面的 @Bean("shiroFilterFactoryBean") public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); //添加shiro的内置过滤器 /* anon: 无需认证就可以访问 authc: 必须认证了才能访问 user: 必须拥有 记住我 功能才能用 perms: 拥有对某个资源的权限才能访问 roles: 拥有某个角色权限才能访问 */ //可以看DefaultFilter,这是一个枚举类,定义了很多的拦截器authc,anon等分别有对应的拦截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //授权,正常的情况下,没有授权会跳到未授权页面 filterChainDefinitionMap.put("/user/add","perms[user:add]"); filterChainDefinitionMap.put("/user/update","perms[user:update]"); filterChainDefinitionMap.put("/user/*", "authc"); //代表着前面的url路径,用后面指定的拦截器进行拦截,此处用的通配符 //filterChainDefinitionMap.put("/admin", "roles[admin]"); //admin的url,要用角色是admin的才可以登录,对应的拦截器是RolesAuthorizationFilter //filterChainDefinitionMap.put("/edit", "perms[edit]"); //拥有edit权限的用户才有资格去访问 bean.setFilterChainDefinitionMap(filterChainDefinitionMap); //设置一个拦截器链 //设置登录的请求 bean.setLoginUrl("/toLogin"); //设置未授权的页面 bean.setUnauthorizedUrl("/noauth"); return bean; } //2.DefaultWebSecurityManager /* * 注入一个securityManager * 原本以前我们是可以通过ini配置文件完成的,代码如下: * 1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); 2、得到SecurityManager实例 并绑定给SecurityUtils SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); * */ @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { //这个DefaultWebSecurityManager构造函数,会对Subject,realm等进行基本的参数注入 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //关联UserRealm securityManager.setRealm(userRealm);//往SecurityManager中注入Realm,代替原本的默认配置 return securityManager; } //1.创建realm对象,需要自定义类 @Bean public UserRealm userRealm() { return new UserRealm(); } //Subject 用户 //SecurityManager 管理所有用户 //Realm 连接数据 }
在 Mycontroller 中,添加未授权跳转的页面
package com.kuang.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; 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; @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);//执行登录的方法,如果没有异常就说明OK啦 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 "未经授权无法访问此页面"; } }
重点:
在 UserRealm 中设置当前用户的权限
package com.kuang.config; import com.kuang.pojo.User; import com.kuang.service.UserService; import org.apache.catalina.mbeans.UserMBean; 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.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; //自定义的UserRealm extends AuthorizationRealm public class UserRealm extends AuthorizingRealm { @Autowired UserService userService; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了=》授权doGetAuthorizationInfo"); //SimpleAuthorizationInfo SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //info.addStringPermission("user:add");//给所有用户添加user:add权限 //拿到当前登录的对象 Subject subject = SecurityUtils.getSubject(); User currentUser = (User) subject.getPrincipal();//拿到User对象 //设置当前用户的权限 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; } //if (!userToken.getUsername().equals(user.getUsername())){ // return null; // 抛出异常,咱自己定义的 UnknownAccountException //} //可以加密,MD5 MD5盐值加密 //密码认证,shiro做 return new SimpleAuthenticationInfo(user,user.getPassword(),""); } }
上面黄色的代码整合了mybatis
public interface UserService { public User queryUserByName(String name); }
<!--shiro-thymeleaf整合--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
在 shiroConfig 配置文件中,整合 shiroDialect
//整合shiroDialect:用来整合 shiro thymeleaf @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); }
在 UserRealm 配置文件中,设置session
//认证 @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 subject = SecurityUtils.getSubject(); Session session = subject.getSession(); session.setAttribute("loginUser", user); //可以加密,MD5 MD5盐值加密 //密码认证,shiro做 return new SimpleAuthenticationInfo(user, user.getPassword(), ""); }
index.html:
判断是否登录,如果登录了,便不显示登录按钮
列举了四种判断方法:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>首页</h1> <div shiro:notAuthenticated=""> <!--<div shiro:guest="true">--> <!--<div th:if="${session.loginUser==null}">--> <!--<div th:if="${session.get('loginUser' eq null)}">--> <a th:href="@{/toLogin}">登录</a> </div> <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>