shiro08 子工程
4.0.0 com.yzm shiro 0.0.1-SNAPSHOT ../pom.xml shiro08 0.0.1-SNAPSHOT jar shiro08 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
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username = (String) principalCollection.getPrimaryPrincipal();
User user = userService.lambdaQuery().eq(User::getUsername, username).one();
List roleIds = Arrays.stream(user.getRIds().split(","))
.map(Integer::parseInt)
.collect(Collectors.toList());
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();
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.shiro08.config;
import com.yzm.shiro08.service.PermissionsService;
import com.yzm.shiro08.service.RoleService;
import com.yzm.shiro08.service.UserService;
import com.yzm.shiro08.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.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import javax.servlet.Filter;
import java.util.linkedHashMap;
import java.util.Map;
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 ServletRegistrationBean initServletRegistrationBean() {
return new ServletRegistrationBean<>(new VerifyServlet(), "/getVerifyCode");
}
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName(EncryptUtils.ALGORITHM_NAME);
hashedCredentialsMatcher.setHashIterations(EncryptUtils.HASH_ITERATIONS);
//hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
@Bean
public MyShiroRealm simpleShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm(userService, roleService, permissionsService);
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
@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(simpleShiroRealm());
securityManager.setRememberMeManager(rememberMeManager());
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());
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setUnauthorizedUrl("/401");
// 自定义拦截器
Map filters = new linkedHashMap<>();
filters.put("verify", new VerifyFilter());
shiroFilterFactoryBean.setFilters(filters);
// 拦截url
Map definitionMap = new linkedHashMap<>();
definitionMap.put("/home", "anon");
definitionMap.put("/getVerifyCode", "anon");
definitionMap.put("/doLogin", "verify");
shiroFilterFactoryBean.setFilterChainDefinitionMap(definitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "/401");
properties.setProperty("org.apache.shiro.authz.UnauthenticatedException", "/login");
simpleMappingExceptionResolver.setExceptionMappings(properties);
return simpleMappingExceptionResolver;
}
}
4 接口
HomeController.java
package com.yzm.shiro08.controller;
import com.yzm.common.entity.HttpResult;
import com.yzm.shiro08.entity.User;
import com.yzm.shiro08.service.UserService;
import com.yzm.shiro08.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();
}
}
AdminController.java
package com.yzm.shiro08.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.shiro08.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";
}
}
5 生成验证码
VerifyServlet.java
package com.yzm.shiro08.config;
import lombok.extern.slf4j.Slf4j;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
@Slf4j
public class VerifyServlet extends HttpServlet {
private static final long serialVersionUID = -5051097528828603895L;
private final int width = 100;
private final int height = 30;
private final int codeCount = 4;
private int codeX;
private int codeY;
private int fontHeight;
private final int interLine = 12;
char[] codeSequence = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
@Override
public void init() throws ServletException {
//width-4 除去左右多余的位置,使验证码更加集中显示,减得越多越集中。
//codeCount+1 等比分配显示的宽度,包括左右两边的空格
codeX = (width - 4) / (codeCount + 1);
//height - 10 集中显示验证码
fontHeight = height - 10;
codeY = height - 7;
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, java.io.IOException {
// 定义图像buffer
BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 获取Graphics对象,便于对图像进行各种绘制操作
Graphics2D gd = buffImg.createGraphics();
// 背景白色
gd.setColor(Color.LIGHT_GRAY);
gd.fillRect(0, 0, width, height);
// 设置字体,字体的大小应该根据图片的高度来定。
gd.setFont(new Font("Times New Roman", Font.PLAIN, fontHeight));
// 画边框。
gd.setColor(Color.BLACK);
gd.drawRect(0, 0, width - 1, height - 1);
// 随机产生干扰线,使图象中的认证码不易被其它程序探测到。
gd.setColor(Color.green);
Random random = new Random();
for (int i = 0; i < interLine; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
gd.drawLine(x, y, x + xl, y + yl);
}
// randomCode用于保存随机产生的验证码,以便用户登录后进行验证。
StringBuilder randomCode = new StringBuilder();
int red, green, blue;
// 随机产生codeCount数字的验证码。
for (int i = 0; i < codeCount; i++) {
// 得到随机产生的验证码数字。
String strRand = String.valueOf(codeSequence[random.nextInt(36)]);
// 产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同。
red = random.nextInt(255);
green = random.nextInt(255);
blue = random.nextInt(255);
// 用随机产生的颜色将验证码绘制到图像中。
gd.setColor(new Color(red, green, blue));
gd.drawString(strRand, (i + 1) * codeX, codeY);
// 将产生的四个随机数组合在一起。
randomCode.append(strRand);
}
// 将四位数字的验证码保存到Session中。
HttpSession session = request.getSession();
session.setAttribute("validateCode", randomCode.toString());
log.info("验证码:" + randomCode);
// 禁止图像缓存。
response.setContentType("image/jpeg");
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
// 将图像输出到Servlet输出流中。
ServletOutputStream sos = response.getOutputStream();
ImageIO.write(buffImg, "jpeg", sos);
sos.close();
}
}
在ShiroConfig中新增
@Bean
public ServletRegistrationBean initServletRegistrationBean() {
return new ServletRegistrationBean<>(new VerifyServlet(), "/getVerifyCode");
}
6 自定义验证码拦截器
VerifyFilter.java
package com.yzm.shiro08.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
@Slf4j
public class VerifyFilter extends AccessControlFilter {
public VerifyFilter() {
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
HttpServletRequest request = WebUtils.toHttp(servletRequest);
//这个validateCode是在servlet中存入session的名字
String validateCode = (String) request.getSession().getAttribute("validateCode");
//获取用户输入的验证码
String inputVerify = request.getParameter("verifyCode");
log.info("用户输入:" + inputVerify);
if (!validateCode.equalsIgnoreCase(inputVerify)) {
WebUtils.issueRedirect(servletRequest, servletResponse, "login?verify");
return false;
}
return true;
}
}
修改login.html
登录页
验证码错误
用户名密码登录
在ShiroConfig#shiroFilter
@Bean
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setUnauthorizedUrl("/401");
// 自定义拦截器
Map filters = new linkedHashMap<>();
filters.put("verify", new VerifyFilter());
shiroFilterFactoryBean.setFilters(filters);
// 拦截url
Map definitionMap = new linkedHashMap<>();
definitionMap.put("/home", "anon");
definitionMap.put("/getVerifyCode", "anon"); // 放行
definitionMap.put("/doLogin", "verify"); // 拦截登录接口
shiroFilterFactoryBean.setFilterChainDefinitionMap(definitionMap);
return shiroFilterFactoryBean;
}
7 测试
启动项目,访问/login
输入错误
输入正确



