1、功能实现
定义两个Realm(User、Admin),然后登录账号,分别认证、授权
2、shiro10 子工程
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.yzm</groupId>
<artifactId>shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>shiro10</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>shiro10</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>com.yzm</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.192.128:3306/testdb2?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
username: root
password: 1234
mybatis-plus:
mapper-locations: classpath:/mapper/*Mapper.xml
type-aliases-package: com.yzm.shiro10.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3、定义UserRealm、AdminRealm
这两个Realm实现的方法内容大致一样,主要通过日志打印查看认证和授权的时候分别调用了什么Realm
package com.yzm.shiro10.config;
import com.yzm.shiro10.entity.Permissions;
import com.yzm.shiro10.entity.Role;
import com.yzm.shiro10.entity.User;
import com.yzm.shiro10.service.PermissionsService;
import com.yzm.shiro10.service.RoleService;
import com.yzm.shiro10.service.UserService;
import lombok.extern.slf4j.Slf4j;
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.util.ByteSource;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 自定义UserRealm
*/
@Slf4j
public class UserRealm extends AuthorizingRealm {
private final UserService userService;
private final RoleService roleService;
private final PermissionsService permissionsService;
public UserRealm(UserService userService, RoleService roleService, PermissionsService permissionsService) {
// 设置Realm名称
setName("UserRealm");
this.userService = userService;
this.roleService = roleService;
this.permissionsService = permissionsService;
}
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof CustomToken;
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("User 授权");
String username = (String) principalCollection.getPrimaryPrincipal();
// 查询用户,获取角色ids
User user = userService.lambdaQuery().eq(User::getUsername, username).one();
List<Integer> roleIds = Arrays.stream(user.getRIds().split(","))
.map(Integer::parseInt)
.collect(Collectors.toList());
// 查询角色,获取角色名、权限ids
List<Role> roles = roleService.listByIds(roleIds);
Set<String> roleNames = new HashSet<>(roles.size());
Set<Integer> permIds = new HashSet<>();
roles.forEach(role -> {
roleNames.add(role.getRName());
Set<Integer> collect = Arrays.stream(
role.getPIds().split(",")).map(Integer::parseInt).collect(Collectors.toSet());
permIds.addAll(collect);
});
// 获取权限名称
List<Permissions> permissions = permissionsService.listByIds(permIds);
List<String> permNames = permissions.stream().map(Permissions::getPName).collect(Collectors.toList());
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRoles(roleNames);
authorizationInfo.addStringPermissions(permNames);
return authorizationInfo;
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("User 认证");
// 获取用户名跟密码
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String username = usernamePasswordToken.getUsername();
// 查询用户是否存在
User user = userService.lambdaQuery().eq(User::getUsername, username).one();
if (user == null) {
throw new UnknownAccountException();
}
return new SimpleAuthenticationInfo(
user.getUsername(),
user.getPassword(),
// 用户名 + 盐
ByteSource.Util.bytes(user.getUsername() + user.getSalt()),
getName()
);
}
}
package com.yzm.shiro10.config;
import com.yzm.shiro10.entity.Permissions;
import com.yzm.shiro10.entity.Role;
import com.yzm.shiro10.entity.User;
import com.yzm.shiro10.service.PermissionsService;
import com.yzm.shiro10.service.RoleService;
import com.yzm.shiro10.service.UserService;
import lombok.extern.slf4j.Slf4j;
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.util.ByteSource;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 自定义AdminRealm
*/
@Slf4j
public class AdminRealm extends AuthorizingRealm {
private final UserService userService;
private final RoleService roleService;
private final PermissionsService permissionsService;
public AdminRealm(UserService userService, RoleService roleService, PermissionsService permissionsService) {
setName("AdminRealm");
this.userService = userService;
this.roleService = roleService;
this.permissionsService = permissionsService;
}
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof CustomToken;
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("Admin 授权");
String username = (String) principalCollection.getPrimaryPrincipal();
// 查询用户,获取角色ids
User user = userService.lambdaQuery().eq(User::getUsername, username).one();
List<Integer> roleIds = Arrays.stream(user.getRIds().split(","))
.map(Integer::parseInt)
.collect(Collectors.toList());
// 查询角色,获取角色名、权限ids
List<Role> roles = roleService.listByIds(roleIds);
Set<String> roleNames = new HashSet<>(roles.size());
Set<Integer> permIds = new HashSet<>();
roles.forEach(role -> {
roleNames.add(role.getRName());
Set<Integer> collect = Arrays.stream(
role.getPIds().split(",")).map(Integer::parseInt).collect(Collectors.toSet());
permIds.addAll(collect);
});
// 获取权限名称
List<Permissions> permissions = permissionsService.listByIds(permIds);
List<String> permNames = permissions.stream().map(Permissions::getPName).collect(Collectors.toList());
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRoles(roleNames);
authorizationInfo.addStringPermissions(permNames);
return authorizationInfo;
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("Admin 认证");
// 获取用户名跟密码
String username = (String) authenticationToken.getPrincipal();
//String password = new String((char[]) authenticationToken.getCredentials());
// 查询用户是否存在
User user = userService.lambdaQuery().eq(User::getUsername, username).one();
if (user == null) {
throw new UnknownAccountException();
}
return new SimpleAuthenticationInfo(
user.getUsername(),
user.getPassword(),
// 用户名 + 盐
ByteSource.Util.bytes(user.getUsername() + user.getSalt()),
getName()
);
}
}
CustomToken 不同角色登录时,通过loginType来区分
package com.yzm.shiro10.config;
import org.apache.shiro.authc.UsernamePasswordToken;
public class CustomToken extends UsernamePasswordToken {
private static final long serialVersionUID = 2496490278578779482L;
private String loginType;
public CustomToken(final String username, final String password) {
super(username, password);
}
public CustomToken(final String username, final String password, String loginType) {
super(username, password);
this.loginType = loginType;
}
public String getLoginType() {
return loginType;
}
public void setLoginType(String loginType) {
this.loginType = loginType;
}
}
LoginType 枚举
package com.yzm.shiro10.config;
public enum LoginType {
USER("User"), ADMIN("Admin");
private final String type;
LoginType(String type) {
this.type = type;
}
public String type() {
return this.type;
}
}
4、登录接口
修改登录接口,通过角色来决定不同的认证授权
package com.yzm.shiro10.controller;
import com.yzm.shiro10.config.CustomToken;
import com.yzm.shiro10.config.LoginType;
import com.yzm.shiro10.entity.User;
import com.yzm.shiro10.service.UserService;
import com.yzm.shiro10.utils.EncryptUtils;
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.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class HomeController {
private final UserService userService;
public HomeController(UserService userService) {
this.userService = userService;
}
@GetMapping(value = {"/", "/home"})
public String home(ModelMap map) {
Subject subject = SecurityUtils.getSubject();
map.addAttribute("subject", subject.getPrincipals());
return "home";
}
@GetMapping("login")
public String login() {
return "login";
}
@GetMapping("401")
public Object notRole() {
return "401";
}
@PostMapping("register")
public Object register(ModelMap map, @RequestParam String username, @RequestParam String password) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
// 密码加密
EncryptUtils.encryptPassword(user);
userService.save(user);
map.addAttribute("user", user);
return "home";
}
@PostMapping("login")
public Object login(@RequestParam String username, @RequestParam String password, boolean rememberMe) {
UsernamePasswordToken token;
if ("admin".equals(username)) {
token = new CustomToken(username, password, LoginType.ADMIN.type());
} else {
token = new CustomToken(username, password, LoginType.USER.type());
}
token.setRememberMe(rememberMe);
String url = "/home";
try {
Subject subject = SecurityUtils.getSubject();
subject.login(token);
} catch (IncorrectCredentialsException e) {
url = "/login?failure";
} catch (UnknownAccountException e) {
url = "/login";
}
return "redirect:" + url;
}
@PostMapping("/logout")
public Object logout() {
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated() || subject.isRemembered()) {
subject.logout();
}
return "redirect:/login";
}
}
5、多Realm认证
自定义MultiRealmAuthenticator,重写doAuthenticate
package com.yzm.shiro10.config;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;
import java.util.ArrayList;
import java.util.Collection;
/**
* 自定义多领域认证器
*/
public class MultiRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
if (authenticationToken instanceof CustomToken) {
assertRealmsConfigured();
CustomToken customToken = (CustomToken) authenticationToken;
// 根据登录类型选择对应的Realm
Collection<Realm> typeRealms = new ArrayList<>();
for (Realm realm : getRealms()) {
if (realm.getName().contains(customToken.getLoginType())) typeRealms.add(realm);
}
return typeRealms.size() == 1 ?
this.doSingleRealmAuthentication(typeRealms.iterator().next(), authenticationToken) :
this.doMultiRealmAuthentication(typeRealms, authenticationToken);
}
return super.doAuthenticate(authenticationToken);
}
}
6、多Realm授权
自定义MultiRealmAuthorizer,重写hasRole、isPermitted
package com.yzm.shiro10.config;
import org.apache.shiro.authz.Authorizer;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.Set;
/**
* 自定义多领域授权器
*/
public class MultiRealmAuthorizer extends ModularRealmAuthorizer {
@Override
public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
assertRealmsConfigured();
// 当前登录人认证时使用的Realm名
Set<String> realmNames = principals.getRealmNames();
String realmName = realmNames.iterator().next();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
// admin
if (realmName.contains(LoginType.ADMIN.type()) && realm instanceof AdminRealm) {
return ((AdminRealm) realm).hasRole(principals, roleIdentifier);
}
// user
if (realmName.contains(LoginType.USER.type()) && realm instanceof UserRealm) {
return ((UserRealm) realm).hasRole(principals, roleIdentifier);
}
}
return false;
}
@Override
public boolean isPermitted(PrincipalCollection principals, String permission) {
assertRealmsConfigured();
// 当前登录人认证时使用的Realm名
Set<String> realmNames = principals.getRealmNames();
String realmName = realmNames.iterator().next();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
// admin
if (realmName.contains(LoginType.ADMIN.type()) && realm instanceof AdminRealm) {
return ((AdminRealm) realm).isPermitted(principals, permission);
}
// user
if (realmName.contains(LoginType.USER.type()) && realm instanceof UserRealm) {
return ((UserRealm) realm).isPermitted(principals, permission);
}
}
return false;
}
}
7、ShiroConfig 配置类
package com.yzm.shiro10.config;
import com.yzm.shiro10.service.PermissionsService;
import com.yzm.shiro10.service.RoleService;
import com.yzm.shiro10.service.UserService;
import com.yzm.shiro10.utils.EncryptUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.mgt.SecurityManager;
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.web.servlet.handler.SimpleMappingExceptionResolver;
import java.util.Arrays;
import java.util.Properties;
@Configuration
public class ShiroConfig {
private final UserService userService;
private final RoleService roleService;
private final PermissionsService permissionsService;
public ShiroConfig(UserService userService, RoleService roleService, PermissionsService permissionsService) {
this.userService = userService;
this.roleService = roleService;
this.permissionsService = permissionsService;
}
/**
* 凭证匹配器
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName(EncryptUtils.ALGORITHM_NAME);
hashedCredentialsMatcher.setHashIterations(EncryptUtils.HASH_ITERATIONS);
return hashedCredentialsMatcher;
}
/**
* UserRealm
*/
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm(userService, roleService, permissionsService);
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return userRealm;
}
/**
* AdminRealm
*/
@Bean
public AdminRealm adminRealm() {
AdminRealm adminRealm = new AdminRealm(userService, roleService, permissionsService);
adminRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return adminRealm;
}
/**
* 安全管理
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 自定义认证器、认证策略
// FirstSuccessfulStrategy:只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略;
// AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,返回所有Realm身份验证成功的认证信息;
// AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。
ModularRealmAuthenticator multiRealmAuthenticator = new MultiRealmAuthenticator();
multiRealmAuthenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
securityManager.setAuthenticator(multiRealmAuthenticator);
// 自定义授权器
ModularRealmAuthorizer multiRealmAuthorizer = new MultiRealmAuthorizer();
securityManager.setAuthorizer(multiRealmAuthorizer);
// 多个Realm
securityManager.setRealms(Arrays.asList(userRealm(), adminRealm()));
return securityManager;
}
/**
* 开启注解
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
@Bean
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
return shiroFilterFactoryBean;
}
/**
* 问题:未登录不会自动跳转到登录页、无权访问页面不跳转
* 原因:Shiro注解模式下,登录失败与没有权限都是通过抛出异常,并且默认并没有去处理或者捕获这些异常。
* 解决:通过在SpringMVC下配置捕获相应异常来通知用户信息
*/
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
// 未登录访问接口跳转到/login、登录后没有权限跳转到/401
properties.setProperty("org.apache.shiro.authz.UnauthenticatedException", "redirect:/login");
properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "redirect:/401");
simpleMappingExceptionResolver.setExceptionMappings(properties);
return simpleMappingExceptionResolver;
}
}
8、测试
登录yzm,访问/user/**
登录admin,也是一样的