第一步:导入依赖:
整合shiro框架
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
页面使用Shiro标签
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
引用Thymeleaf
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
第二步:创建Realm文件
@Component
public class CustomRealm extends AuthorizingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
System.out.println("[FirstRealm] doGetAuthenticationInfo");
//1. 把 AuthenticationToken 转换为 UsernamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//2. 从 UsernamePasswordToken 中来获取 username
String username = upToken.getUsername();
//3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
System.out.println("从数据库中获取 username: " + username + " 所对应的用户信息.");
//4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
if("unknown".equals(username)){
throw new UnknownAccountException("用户不存在!");
}
//5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常.
if("monster".equals(username)){
throw new LockedAccountException("用户被锁定");
}
//6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
//以下信息是从数据库中获取的.
//1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象.
Object principal = username;
//2). credentials: 密码.
Object credentials = null; //"fc1709d0a95a6be30bc5926fdb7f22f4";
if("admin".equals(username)){
credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";
}else if("user".equals(username)){
credentials = "098d2c478e9c11555ce2823231e02ec1";
}
//3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可
String realmName = getName();
//4). 盐值.
ByteSource credentialsSalt = ByteSource.Util.bytes(username);
SimpleAuthenticationInfo info = null; //new SimpleAuthenticationInfo(principal, credentials, realmName);
info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
return info;
}
//授权会被 shiro 回调的方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
//1. 从 PrincipalCollection 中来获取登录用户的信息
Object principal = principals.getPrimaryPrincipal();
//2. 利用登录的用户的信息来用户当前用户的角色或权限(可能需要查询数据库)
Set<String> roles = new HashSet<>();
roles.add("user");
if("admin".equals(principal)){
roles.add("admin");
}
//3. 创建 SimpleAuthorizationInfo, 并设置其 reles 属性.
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
//4. 返回 SimpleAuthorizationInfo 对象.
return info;
}
}
第三步:创建ShiroConfig文件 等同于 在spring中的applicationContext.xml
注意:包名别导入错误
import com.boot.shiro.bootshiro.realms.CustomRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.*;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author GuoLeiCode
* @version 1.0
* @date 2020/10/5 19:25
*/
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/shiro/login.html");
shiroFilterFactoryBean.setSuccessUrl("/shiro/list.html");
shiroFilterFactoryBean.setUnauthorizedUrl("/shiro/notRole");
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/shiro/login.html", "anon");
filterChainDefinitionMap.put("/shiro/doLogin", "anon");
filterChainDefinitionMap.put("/shiro/logout", "logout");
filterChainDefinitionMap.put("/shiro/user.html", "authc,roles[user]");
filterChainDefinitionMap.put("/shiro/admin.html", "authc,roles[admin]");
filterChainDefinitionMap.put("/shiro/list.html", "user");
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean("securityManager")
public SecurityManager securityManager() {
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
//第一种:一个Realm文件
defaultSecurityManager.setRealm(customRealm()); //只用一个Realm文件
//第二种: 多个Realm文件,使用下面两行的代码
//defaultSecurityManager.setRealms(realmList()); // 使用多个Realm
//defaultSecurityManager.setAuthenticator(authenticator()); // 多个Realm -> 选择认证的的方式
return defaultSecurityManager;
}
//设置单个Realm,也可以使用 @Component 注册到spring容器中,不使用new的操作
@Bean
public CustomRealm customRealm() {
CustomRealm customRealm = new CustomRealm();
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5"); //还可以是SHA1
hashedCredentialsMatcher.setHashIterations(1024); // 加密次数
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
customRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return customRealm;
}
/**
*在SecurityManager中设置多个Realm时,使用下面的注释
*/
// //设置多个Realm
// @Bean
// public List<Realm> realmList(){
// List<Realm> realms = new ArrayList<>();
// CustomRealm customRealm = new CustomRealm();
// realms.add(customRealm);
// return realms;
// }
// //设置多个Realm的认证方式
// @Bean
// public ModularRealmAuthenticator authenticator(){
// ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
AuthenticationStrategy接口默认实现:
① FirstSuccessfulStrategy: 只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他忽略
② AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功,将返回所有Realm身份验证成功的认证信息
③ AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,一个失败就失败了
ModularRealmAuthenticator默认是第②种
// modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
// modularRealmAuthenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
// modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());
// return modularRealmAuthenticator;
// }
/**
* 2、配置 LifecycleBeanPostProcessor. 可以自定的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法.
*/
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 3、开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
*
* 配置以下两个bean(
* DefaultAdvisorAutoProxyCreator(可选)、
* AuthorizationAttributeSourceAdvisor即可实现此功能
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
applicationContext.xml 和 代码 对照关系
第四步:控制器和页面的编写进行测试
@Controller
@RequestMapping("/shiro")
public class ShiroController {
@RequestMapping(path = "/login.html")
public String toLogin(){
return "login";
}
@RequestMapping("/list.html")
public String toList(){
return "list";
}
@RequestMapping("/user.html")
public String toUser(){
return "user";
}
@RequestMapping({"/unauthorized","/unauthorized.html"})
public String toUnauthorized(){
return "unauthorized";
}
@RequestMapping(path = {"/admin.html"})
public String toAdmin(){
return "admin";
}
@ResponseBody
@RequestMapping("notRole")
public String noRole(){return "not Role";}
@RequestMapping("/doLogin")
public String doLogin(@RequestParam("username") String username,
@RequestParam("password") String password){
Subject currentUser = SecurityUtils.getSubject();
if (!currentUser.isAuthenticated()) {
// 把用户名和密码封装为 UsernamePasswordToken 对象
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// rememberme
token.setRememberMe(true);
try {
System.out.println("1. " + token.hashCode());
// 执行登录.
currentUser.login(token);
}
// ... catch more exceptions here (maybe custom ones specific to your application?
// 所有认证时异常的父类.
catch (AuthenticationException ae) {
//unexpected condition? error?
System.out.println("登录失败: " + ae.getMessage());
}
}
return "redirect:/shiro/list.html";
}
}
页面创建,每个页面有自己独特的标识即可
示例list.html ;使用了shiro标签:需要提前引入对应的标签
<!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>list页面</title>
</head>
<body>
<h4>List Page</h4>
Welcome: <shiro:principal></shiro:principal>
<shiro:hasRole name="admin">
<br><br>
<a href="/shiro/admin.html">Admin Page</a>
</shiro:hasRole>
<shiro:hasRole name="user">
<br><br>
<a href="/shiro/user.html">User Page</a>
</shiro:hasRole>
<br><br>
<a href="/shiro/logout">Logout</a></body>
</html>