4.0.0 com.yzm shiro 0.0.1-SNAPSHOT ../pom.xml shiro03 0.0.1-SNAPSHOT jar shiro03 Demo project for Spring Boot com.yzm common 0.0.1-SNAPSHOT org.springframework.boot spring-boot-maven-plugin
项目结构
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test3?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
username: root
password: root
mybatis-plus:
mapper-locations: classpath:/mapper
public class MyShiroRealm extends AuthorizingRealm {
private final UserService userService;
private final RoleService roleService;
private final PermissionsService permissionsService;
public MyShiroRealm(UserService userService, RoleService roleService, PermissionsService permissionsService) {
this.userService = userService;
this.roleService = roleService;
this.permissionsService = permissionsService;
}
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username = (String) principalCollection.getPrimaryPrincipal();
// 查询用户,获取角色ids
User user = userService.lambdaQuery().eq(User::getUsername, username).one();
List roleIds = Arrays.stream(user.getRIds().split(","))
.map(Integer::parseInt)
.collect(Collectors.toList());
// 查询角色,获取角色名、权限ids
List roles = roleService.listByIds(roleIds);
Set roleNames = new HashSet<>(roles.size());
Set permIds = new HashSet<>();
roles.forEach(role -> {
roleNames.add(role.getRName());
Set collect = Arrays.stream(
role.getPIds().split(",")).map(Integer::parseInt).collect(Collectors.toSet());
permIds.addAll(collect);
});
// 获取权限名称
List permissions = permissionsService.listByIds(permIds);
List 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 {
// 获取用户名跟密码
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String username = usernamePasswordToken.getUsername();
// 也可以这样获取
//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()
);
}
}
ShiroConfig 配置类
package com.yzm.shiro03.config;
import com.yzm.shiro03.service.PermissionsService;
import com.yzm.shiro03.service.RoleService;
import com.yzm.shiro03.service.UserService;
import com.yzm.shiro03.utils.EncryptUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.cookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.cookie;
import org.apache.shiro.web.servlet.Simplecookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
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);
//true加密用的hex编码,false用的base64编码;默认true,本实例是toHex,可以查看EncryptUtils
//hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
@Bean
public MyShiroRealm shiroRealm() {
MyShiroRealm shiroRealm = new MyShiroRealm(userService, roleService, permissionsService);
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shiroRealm;
}
@Bean
public cookie simplecookie() {
Simplecookie cookie = new Simplecookie("rememberMe");
//设为true后,只能通过http访问,javascript无法访问
//防止xss读取cookie
cookie.setHttpOnly(true);
cookie.setPath("/");
//存活时间,单位秒;-1表示关闭浏览器该cookie失效
cookie.setMaxAge(-1);
return cookie;
}
@Bean
public cookieRememberMeManager rememberMeManager() {
cookieRememberMeManager rememberMeManager = new cookieRememberMeManager();
rememberMeManager.setcookie(simplecookie());
//cookie加密的密钥
//rememberMeManager.setCipherKey(base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
return rememberMeManager;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 配置单个realm
securityManager.setRealm(shiroRealm());
// 记住我
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
// setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
shiroFilterFactoryBean.setLoginUrl("/login");
// 设置无权限时跳转的 url
shiroFilterFactoryBean.setUnauthorizedUrl("/401");
// Map definitionMap = new linkedHashMap<>();
// definitionMap.put("/user
@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 SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
// 登录后没有权限跳转到/401
properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "/401");
// 未登录访问接口跳转到/login
properties.setProperty("org.apache.shiro.authz.UnauthenticatedException", "/login");
simpleMappingExceptionResolver.setExceptionMappings(properties);
return simpleMappingExceptionResolver;
}
@Bean
public WebServerFactoryCustomizer webServerFactoryCustomizer() {
return factory -> {
ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/401");
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/404");
ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500");
factory.addErrorPages(error401Page, error404Page, error500Page);
};
}
}
4 开启注解
@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;
}
5 注解使用
@RequiresRoles 等同于 roles @RequiresPermissions 等同于 perms @RequiresAuthentication 等同于 authc @RequiresUser 等同于 user @RequiresGuest 跟 @RequiresUser 完全相反,即未登录也不能使用记住我功能,只能是游客
HomeController.java
package com.yzm.shiro03.controller;
import com.yzm.common.entity.HttpResult;
import com.yzm.shiro03.entity.User;
import com.yzm.shiro03.service.UserService;
import com.yzm.shiro03.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.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresGuest;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Controller
public class HomeController {
private final UserService userService;
public HomeController(UserService userService) {
this.userService = userService;
}
@GetMapping("login")
public String login() {
return "login";
}
@GetMapping("home")
public Object home() {
return "home";
}
@GetMapping("401")
public Object notRole() {
return "401";
}
@PostMapping("register")
@ResponseBody
public Object register(@RequestParam String username, @RequestParam String password) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
EncryptUtils.encryptPassword(user);
userService.save(user);
return HttpResult.ok();
}
@PostMapping("doLogin")
@ResponseBody
public Object doLogin(@RequestParam String username, @RequestParam String password, boolean rememberMe) {
try {
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
if (rememberMe) usernamePasswordToken.setRememberMe(true);
Subject subject = SecurityUtils.getSubject();
subject.login(usernamePasswordToken);
} catch (IncorrectCredentialsException ice) {
return HttpResult.error("password error!");
} catch (UnknownAccountException uae) {
return HttpResult.error("username error!");
}
return HttpResult.ok();
}
// 自定义退出接口
@GetMapping("/logout")
public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException {
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated() || subject.isRemembered()) {
subject.logout();
}
response.sendRedirect(request.getContextPath() + "/login");
}
@GetMapping("hello")
@RequiresGuest //登录状态不能访问
@ResponseBody
public Object hello() {
return "hello";
}
@GetMapping("list")
@RequiresUser // 使用记住我功能时可以访问
@ResponseBody
public Object userList() {
return userService.list();
}
@GetMapping("list2")
@RequiresAuthentication
@ResponseBody
public Object userList2() {
return userService.list();
}
@GetMapping("404")
public Object notFound() {
return "404";
}
@GetMapping("500")
public Object error() {
return "500";
}
@GetMapping("/fail")
@ResponseBody
public Object fail() {
int i = 1/0;
return "出错了";
}
}
AdminController.java
package com.yzm.shiro03.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/admin")
@RequiresRoles("ADMIN")
public class AdminController {
@GetMapping
public Object admin() {
return SecurityUtils.getSubject().getPrincipal();
}
@GetMapping("select")
@RequiresPermissions("admin:select")
public Object select() {
return "Select";
}
@GetMapping("create")
@RequiresPermissions("admin:create")
public Object create() {
return "Create";
}
@GetMapping("update")
@RequiresPermissions("admin:update")
public Object update() {
return "Update";
}
@GetMapping("delete")
@RequiresPermissions("admin:delete")
public Object delete() {
return "Delete";
}
}
UserController.java
package com.yzm.shiro03.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("//user")
@RequiresRoles(value = {"USER", "ADMIN"}, logical = Logical.OR)
public class UserController {
@GetMapping
public Object user() {
return SecurityUtils.getSubject().getPrincipal();
}
@GetMapping("select")
@RequiresPermissions("user:select")
public Object select() {
return "Select";
}
@GetMapping("create")
@RequiresPermissions("user:create")
public Object create() {
return "Create";
}
@GetMapping("update")
@RequiresPermissions("user:update")
public Object update() {
return "Update";
}
@GetMapping("delete")
@RequiresPermissions("user:delete")
public Object delete() {
return "Delete";
}
@GetMapping("createAndUpdate")
//需要同时拥有
@RequiresPermissions(value = {"user:create", "user:update"}, logical = Logical.AND)
public Object createAndUpdate() {
return "Create And Update";
}
@GetMapping("createOrUpdate")
//拥有其中任意一个即可
@RequiresPermissions(value = {"user:create", "user:update"}, logical = Logical.OR)
public Object createOrUpdate() {
return "Create Or Update";
}
}
6 html页面
home.html
首页
注册
admin
Admin角色
user
User角色
User角色,拥有 create and update 权限
记住我
注解
401.html跟login.html页面不需要改动
7 测试启动项目,访问/home,当前未登录,点击admin或user下面的接口
全部都会报错的,控制台打印错误信息,而不是像之前一样跳转到登录页
shiroFilterFactoryBean.setLoginUrl("/login");
先不理它,访问/login 登录yzm用户
登录成功,继续访问
yzm的接口部分能访问,而admin的接口还是报错
这是由于没有权限原因,当我们使用注解方式时,访问没有权限的接口不会被shiroFilterFactoryBean.setUnauthorizedUrl("/401");处理
MyExceptionHandler.java
package com.yzm.shiro03.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@RestControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler
public void ErrorHandler(AuthorizationException e, HttpServletRequest request, HttpServletResponse response) throws IOException {
log.error("注解方式,自定义异常捕获");
if (e instanceof UnauthenticatedException) {
WebUtils.issueRedirect(request, response, "/login");
} else if (e instanceof UnauthorizedException) {
WebUtils.issueRedirect(request, response, "/401");
}
}
}
重新启动项目,未登录跳转到登录页,没权限跳转到401
在shiro配置类,注入
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
// 登录后没有权限跳转到/401
properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "/401");
// 未登录访问接口跳转到/login
properties.setProperty("org.apache.shiro.authz.UnauthenticatedException", "/login");
simpleMappingExceptionResolver.setExceptionMappings(properties);
return simpleMappingExceptionResolver;
}
注释掉解决方法一的全局捕获
不然还是会被全局的捕获,导致上面注入的方法无效
@Slf4j
//@RestControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler
public void ErrorHandler(AuthorizationException e, HttpServletRequest request, HttpServletResponse response) throws IOException {
log.error("注解方式,自定义异常捕获");
if (e instanceof UnauthenticatedException) {
WebUtils.issueRedirect(request, response, "/login");
} else if (e instanceof UnauthorizedException) {
WebUtils.issueRedirect(request, response, "/401");
}
}
}
重新启动项目,未登录时访问跳转到登录页,登录后访问没有权限跳转到/401,
7 自定义错误页面访问/hello 测试@RequiresGuest,未登录可以访问;认证过或使用记住我功能拒绝访问
访问/list2 测试@RequiresAuthentication 认证过可以访问,其他时候拒绝访问
访问/list 测试@RequiresUser 认证过或使用记住我功能可以访问@RequiresPermissions(value = {“user:create”, “user:update”}, logical = Logical.AND)
同时具备2个权限才能访问
@RequiresPermissions(value = {“user:create”, “user:update”}, logical = Logical.OR)
只需要拥有其中任意一个权限就可以访问
@RequiresRoles 跟 @RequiresPermissions 使用差不多的
在shiro配置了中添加
@Bean
public WebServerFactoryCustomizer webServerFactoryCustomizer() {
return factory -> {
// 可以这样写,但效果没有上面的两种方案好
//ErrorPage errorLoginPage = new ErrorPage(UnauthenticatedException.class, "/login");
//ErrorPage error401Page = new ErrorPage(UnauthorizedException.class, "/401");
ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/401");
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/404");
ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500");
factory.addErrorPages(error401Page, error404Page, error500Page);
};
}
在HomeController.java 新增接口
@GetMapping("/fail")
@ResponseBody
public Object fail() {
int i = 1/0;
return "出错了";
}
@GetMapping("404")
public Object notFound() {
return "404";
}
@GetMapping("500")
public Object error() {
return "500";
}
新增404、500页面,内容跟401差不多
500
服务器崩溃了
重启,访问/fail 跳转到500页面,访问不存在的url跳转到404页面
8 注解跟拦截器同时使用,拦截器优先例如:在ShiroConfig#shiroFilter
@Bean
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
// setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
shiroFilterFactoryBean.setLoginUrl("/login");
// 设置无权限时跳转的 url
shiroFilterFactoryBean.setUnauthorizedUrl("/401");
Map definitionMap = new linkedHashMap<>();
definitionMap.put("/user/**", "roles[ADMIN]");
shiroFilterFactoryBean.setFilterChainDefinitionMap(definitionMap);
return shiroFilterFactoryBean;
}
debug role注解处理器和role拦截器
重启,登录yzm,访问/user,先进的是拦截器,但拦截器配置的需要admin角色,yzm只有user角色
所以直接跳转到401页面,不再继续注解处理器
如果拦截器通过,就还会继续注解处理器,只有都通过才能访问



