SpringBoot整合Shiro实现权限控制,验证码
本文介绍 SpringBoot 整合 shiro,相对于 Spring Security 而言,shiro 更加简单,没有那么复杂。
目前我的需求是一个博客系统,有用户和管理员两种角色。一个用户可能有多个角色,每个角色可能有多个权限,每个角色关联不同的菜单(也可以权限和菜单关联)。
本文主要介绍 Shiro 的使用,这里只介绍用户和角色,不需要权限也行。
一、数据库设计
三张表:user、role、user_role
- -- ----------------------------
- -- Table structure for `role`
- -- ----------------------------
- DROP TABLE IF EXISTS `role`;
- CREATE TABLE `role` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `description` varchar(255) DEFAULT NULL,
- `role` varchar(255) DEFAULT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
- -- ----------------------------
- -- Table structure for `user`
- -- ----------------------------
- DROP TABLE IF EXISTS `user`;
- CREATE TABLE `user` (
- `id` int(10) NOT NULL AUTO_INCREMENT,
- `password` varchar(100) NOT NULL,
- `username` varchar(20) NOT NULL COMMENT '用于登录的账号',
- `display_name` varchar(20) DEFAULT NULL COMMENT '显示的用户名',
- `email` varchar(100) DEFAULT NULL COMMENT '电子邮箱',
- `url` varchar(255) DEFAULT NULL COMMENT '个人主页',
- `avatar` varchar(255) DEFAULT NULL COMMENT '头像',
- `profile` varchar(255) DEFAULT NULL COMMENT '简介',
- `create_time` datetime DEFAULT NULL,
- `last_login_time` datetime DEFAULT NULL,
- `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '正常1,禁止登录0, 已删除-1',
- PRIMARY KEY (`id`),
- UNIQUE KEY `uq_user_username` (`username`) USING BTREE,
- UNIQUE KEY `uq_user_displayname` (`display_name`) USING BTREE,
- UNIQUE KEY `uq_user_email` (`email`) USING BTREE
- ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
- -- ----------------------------
- -- Table structure for `user_role`
- -- ----------------------------
- DROP TABLE IF EXISTS `user_role`;
- CREATE TABLE `user_role` (
- `role_id` int(11) NOT NULL,
- `user_id` int(11) NOT NULL,
- KEY `role_id` (`role_id`),
- KEY `user_id` (`user_id`)
- ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
- SET FOREIGN_KEY_CHECKS = 1;
二、SpringBoot 整合 Shiro
1、添加 shiro 依赖
2、MyShiroRealm.java
- package com.liuyanzhao.blog.web.config;
- import com.liuyanzhao.blog.api.model.Role;
- import com.liuyanzhao.blog.api.model.User;
- import com.liuyanzhao.blog.api.service.RoleService;
- import com.liuyanzhao.blog.api.service.UserService;
- import com.liuyanzhao.blog.api.util.Response;
- import com.liuyanzhao.blog.web.enums.UserStatus;
- 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.springframework.beans.factory.annotation.Autowired;
- import java.util.List;
- /**
- * @author 言曌
- * @date 2018/9/1 上午10:47
- */
- public class MyShiroRealm extends AuthorizingRealm {
- @Autowired
- private UserService userService;
- @Autowired
- private RoleService roleService;
- public static final String SALT = "com.liuyanzhao";
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
- SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
- User user = (User) principals.getPrimaryPrincipal();
- List<Role> roleList = roleService.listRolesByUser(user);
- for (Role role : roleList) {
- authorizationInfo.addRole(role.getRole());
- }
- return authorizationInfo;
- }
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
- throws AuthenticationException {
- System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
- //获取用户的输入的账号.
- String username = (String) token.getPrincipal();
- System.out.println(token.getCredentials());
- //通过username从数据库中查找 User对象,如果找到,没找到.
- //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
- Response<User> response = userService.getUserByUsername(username);
- if (!response.getSuccess()) {
- return null;
- }
- User user = response.getData();
- if (UserStatus.LOCKED.getCode().equals(user.getStatus())) {
- throw new LockedAccountException(username + "账号被锁定,请联系管理员!");
- }
- SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
- user,
- user.getPassword(),
- getName()
- );
- return authenticationInfo;
- }
- }
这个是自定义验证账号密码和验证是否有权限。
其中 UserService 和 RoleService 这里就不用给了,一个是根据用户名获得用户,一个是根据获得用户的权限列表。
3、ShiroConfig.java
- package com.liuyanzhao.blog.web.config;
- 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.DefaultWebSecurityManager;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
- import java.util.LinkedHashMap;
- import java.util.Map;
- import java.util.Properties;
- /**
- * @author 言曌
- * @date 2018/8/20 上午6:19
- */
- @Configuration
- public class ShiroConfig {
- @Bean
- public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
- System.out.println("ShiroConfiguration.shirFilter()");
- ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
- shiroFilterFactoryBean.setSecurityManager(securityManager);
- //拦截器.
- Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
- // 配置不会被拦截的链接 顺序判断
- filterChainDefinitionMap.put("/css/**", "anon");
- filterChainDefinitionMap.put("/js/**", "anon");
- filterChainDefinitionMap.put("/img/**", "anon");
- filterChainDefinitionMap.put("/components/**", "anon");
- filterChainDefinitionMap.put("/favicon.ico", "anon");
- //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
- filterChainDefinitionMap.put("/logout", "logout");
- //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
- //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
- filterChainDefinitionMap.put("/admin/**", "authc");
- filterChainDefinitionMap.put("/user/**", "authc");
- filterChainDefinitionMap.put("/**", "anon");
- shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
- // 如果不设置默认会自动寻找Web工程根目录下的"/login"页面
- shiroFilterFactoryBean.setLoginUrl("/login");
- // 登录成功后要跳转的链接
- shiroFilterFactoryBean.setSuccessUrl("/");
- //未授权界面;
- shiroFilterFactoryBean.setUnauthorizedUrl("/403");
- return shiroFilterFactoryBean;
- }
- /**
- * 凭证匹配器
- * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
- * )
- * @return
- */
- @Bean
- public HashedCredentialsMatcher hashedCredentialsMatcher(){
- HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
- //散列算法:这里使用MD5算法;
- hashedCredentialsMatcher.setHashAlgorithmName("md5");
- //散列的次数,比如散列两次,相当于 md5(md5(""));
- hashedCredentialsMatcher.setHashIterations(2);
- return hashedCredentialsMatcher;
- }
- @Bean
- public MyShiroRealm myShiroRealm(){
- MyShiroRealm myShiroRealm = new MyShiroRealm();
- myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
- return myShiroRealm;
- }
- @Bean
- public SecurityManager securityManager(){
- DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
- //设置realm
- securityManager.setRealm(myShiroRealm());
- return securityManager;
- }
- /**
- * 开启shiro aop注解支持.
- * 使用代理方式;所以需要开启代码支持;
- * @param securityManager
- * @return
- */
- @Bean
- public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
- AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
- authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
- return authorizationAttributeSourceAdvisor;
- }
- @Bean(name="simpleMappingExceptionResolver")
- public SimpleMappingExceptionResolver
- createSimpleMappingExceptionResolver() {
- SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
- Properties mappings = new Properties();
- //数据库异常处理
- mappings.setProperty("DatabaseException", "databaseError");
- mappings.setProperty("UnauthorizedException","403");
- r.setExceptionMappings(mappings);
- r.setDefaultErrorView("error");
- r.setExceptionAttribute("message");
- return r;
- }
- }
这里补充一下链接器链,是按顺序匹配的。必须使用LinkedHashMap,因为HashMap遍历是无序的。
目前我是放行除 /admin/** 和 /user/** 之外所有的页面,通常情况下是放行匿名的页面,其他的一律需要授权验证,如
filterChainDefinitionMap.put("/**", "authc");
4、LoginParam.java
- package com.liuyanzhao.blog.web.param;
- import lombok.Data;
- import java.io.Serializable;
- /**
- * 登录参数
- * @author 言曌
- * @date 2018/9/9 上午11:42
- */
- @Data
- public class LoginParam implements Serializable{
- private static final long serialVersionUID = 166457193110647497L;
- private String username;
- private String password;
- private String captchaCode;
- private boolean rememberMe;
- private String CSRFToken;
- }
5、LoginController.java
- /**
- * 登录页面
- *
- * @return
- */
- @GetMapping("/login")
- public String loginPage() {
- return "login";
- }
- /**
- * 登录提交
- *
- * @param loginParam
- * @param model
- * @return
- * @throws Exception
- */
- @PostMapping("/login")
- public String login(LoginParam loginParam,
- Model model) throws Exception {
- //1、验证用户名和密码
- org.apache.shiro.subject.Subject subject = SecurityUtils.getSubject();
- UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginParam.getUsername(), loginParam.getPassword(), loginParam.isRememberMe());
- String msg = "";
- try {
- subject.login(usernamePasswordToken);
- return "redirect:/";
- } catch (UnknownAccountException e) {
- log.info("UnknownAccountException -- > 账号不存在:");
- msg = "账号不存在!";
- } catch (IncorrectCredentialsException e) {
- log.info("IncorrectCredentialsException -- > 密码不正确:");
- msg = "密码不正确!";
- } catch (LockedAccountException e) {
- log.info("LockedAccountException -- > 账号被锁定");
- msg = "账号被锁定!";
- } catch (Exception e) {
- log.info(e.getMessage());
- }
- model.addAttribute("msg", msg);
- return "login";
- }
6、login.html
- <form name="loginform" id="loginform" action="/login" method="post">
- <p>
- <label for="username">用户名或电子邮件地址<br/>
- <input type="text" name="username" id="username" class="input" size="20" required/>
- </label>
- </p>
- <p>
- <label for="password">密码<br/>
- <input type="password" name="password" id="password" class="input" size="20" required/>
- </label>
- </p>
- <p th:if="${msg}">
- <label for="captchaCode">验证码<br/>
- <input type="text" name="captchaCode" id="captchaCode" class="input" size="20"
- style="float:left;width: 40%; " required/>
- <img src="/img/getKaptchaImage" alt="" style="float:left;padding-top: 3px;">
- <span>换一张</span>
- </label>
- </p>
- <input type="hidden" name="CSRFToken" th:value="${session.CSRFToken}">
- <div style="clear: both;"></div>
- <p class="forgetmenot">
- <label for="rememberme">
- <input name="rememberMe" type="checkbox" id="rememberMe"
- checked="checked"> 记住我的登录信息</label>
- </p>
- <p class="submit">
- <input type="submit" class="button button-primary button-large" value="登录"/>
- </p>
- <br>
- </form>
这里主要关注 form 表单提交的 username 和 password 即可,其中 CSRF 防护忽略,rememberMe 下面要用到。
三、添加 kaptcha 验证码
1、添加验证码依赖
- <!--验证码-->
- <dependency>
- <groupId>com.github.penggle</groupId>
- <artifactId>kaptcha</artifactId>
- <version>2.3.2</version>
- </dependency>
2、验证码配置类
- package com.liuyanzhao.blog.web.config;
- import com.google.code.kaptcha.impl.DefaultKaptcha;
- import com.google.code.kaptcha.util.Config;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import java.util.Properties;
- /**
- * 验证码图片样式配置
- * @author 言曌
- * @date 2018/9/2 上午10:23
- */
- @Configuration
- public class KaptchaConfig {
- @Bean(name="captchaProducer")
- public DefaultKaptcha getKaptchaBean(){
- DefaultKaptcha defaultKaptcha=new DefaultKaptcha();
- Properties properties=new Properties();
- //验证码字符范围
- properties.setProperty("kaptcha.textproducer.char.string", "23456789");
- //图片边框颜色
- properties.setProperty("kaptcha.border.color", "245,248,249");
- //字体颜色
- properties.setProperty("kaptcha.textproducer.font.color", "black");
- //文字间隔
- properties.setProperty("kaptcha.textproducer.char.space", "1");
- //图片宽度
- properties.setProperty("kaptcha.image.width", "100");
- //图片高度
- properties.setProperty("kaptcha.image.height", "35");
- //字体大小
- properties.setProperty("kaptcha.textproducer.font.size", "30");
- //session的key
- // properties.setProperty("kaptcha.session.key", "code");
- //长度
- properties.setProperty("kaptcha.textproducer.char.length", "4");
- //字体
- properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
- Config config=new Config(properties);
- defaultKaptcha.setConfig(config);
- return defaultKaptcha;
- }
- }
3、验证码控制器
- package com.liuyanzhao.blog.web.controller.common;
- import com.google.code.kaptcha.Constants;
- import com.google.code.kaptcha.Producer;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.GetMapping;
- import javax.imageio.ImageIO;
- import javax.servlet.ServletOutputStream;
- import java.awt.image.BufferedImage;
- /**
- * 验证码控制器
- * @author 言曌
- * @date 2018/9/2 上午10:41
- */
- @Controller
- @Slf4j
- public class KaptchaController extends BaseController {
- @Autowired
- private Producer captchaProducer;
- @GetMapping("/img/getKaptchaImage")
- public void getKaptchaImage() throws Exception {
- response.setDateHeader("Expires", 0);
- // Set standard HTTP/1.1 no-cache headers.
- response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
- // Set IE extended HTTP/1.1 no-cache headers (use addHeader).
- response.addHeader("Cache-Control", "post-check=0, pre-check=0");
- // Set standard HTTP/1.0 no-cache header.
- response.setHeader("Pragma", "no-cache");
- // return a jpeg
- response.setContentType("image/jpeg");
- // create the text for the image
- String capText = captchaProducer.createText();
- //将验证码存到session
- session.setAttribute(Constants.KAPTCHA_SESSION_KEY, capText);
- log.info(capText);
- // create the image with the text
- BufferedImage bi = captchaProducer.createImage(capText);
- ServletOutputStream out = response.getOutputStream();
- // write the data out
- ImageIO.write(bi, "jpg", out);
- try {
- out.flush();
- } finally {
- out.close();
- }
- }
- }
4、修改 LoginController.java
- /**
- * 登录提交
- *
- * @param loginParam
- * @param model
- * @return
- * @throws Exception
- */
- @PostMapping("/login")
- public String login(LoginParam loginParam,
- Model model) throws Exception {
- //1、检验验证码
- if (loginParam.getCaptchaCode() != null) {
- String inputCode = request.getParameter("captchaCode");
- String captchaSession = (String) session.getAttribute(Constants.KAPTCHA_SESSION_KEY);
- if (!Objects.equals(inputCode, captchaSession)) {
- log.info("验证码错误,用户输入:{}, 正确验证码:{}", inputCode, captchaSession);
- model.addAttribute("msg", "验证码不正确!");
- CsrfTokenUtil.refreshToken(request);
- return "login";
- }
- }
- //2、验证用户名和密码
- org.apache.shiro.subject.Subject subject = SecurityUtils.getSubject();
- UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginParam.getUsername(), loginParam.getPassword());
- String msg = "";
- try {
- subject.login(usernamePasswordToken);
- return "redirect:/";
- } catch (UnknownAccountException e) {
- log.info("UnknownAccountException -- > 账号不存在:");
- msg = "账号不存在!";
- } catch (IncorrectCredentialsException e) {
- log.info("IncorrectCredentialsException -- > 密码不正确:");
- msg = "密码不正确!";
- } catch (LockedAccountException e) {
- log.info("LockedAccountException -- > 账号被锁定");
- msg = "账号被锁定!";
- } catch (Exception e) {
- log.info(e.getMessage());
- }
- model.addAttribute("msg", msg);
- CsrfTokenUtil.refreshToken(request);
- return "login";
- }
四、配置记住我
1、修改 ShiroConfig.java
- /**
- * cookie对象;
- * rememberMeCookie()方法是设置Cookie的生成模版,比如cookie的name,cookie的有效时间等等。
- * @return
- */
- @Bean
- public SimpleCookie rememberMeCookie(){
- //System.out.println("ShiroConfiguration.rememberMeCookie()");
- //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
- SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
- //<!-- 记住我cookie生效时间30天 ,单位秒;-->
- simpleCookie.setMaxAge(259200);
- return simpleCookie;
- }
- /**
- * cookie管理对象;
- * rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中
- * @return
- */
- @Bean
- public CookieRememberMeManager rememberMeManager(){
- //System.out.println("ShiroConfiguration.rememberMeManager()");
- CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
- cookieRememberMeManager.setCookie(rememberMeCookie());
- //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
- cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
- return cookieRememberMeManager;
- }
- @Bean
- public SecurityManager securityManager(){
- DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
- //设置realm
- securityManager.setRealm(myShiroRealm());
- //用户授权/认证信息Cache, 采用EhC//注入记住我管理器
- securityManager.setRememberMeManager(rememberMeManager());
- return securityManager;
- }
2、修改 LoginController.java
主要是修改
- UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginParam.getUsername(), loginParam.getPassword(), loginParam.isRememberMe());
最终如下
- /**
- * 登录提交
- *
- * @param loginParam
- * @param model
- * @return
- * @throws Exception
- */
- @PostMapping("/login")
- public String login(LoginParam loginParam,
- Model model) throws Exception {
- //1、检验验证码
- if (loginParam.getCaptchaCode() != null) {
- String inputCode = request.getParameter("captchaCode");
- String captchaSession = (String) session.getAttribute(Constants.KAPTCHA_SESSION_KEY);
- if (!Objects.equals(inputCode, captchaSession)) {
- log.info("验证码错误,用户输入:{}, 正确验证码:{}", inputCode, captchaSession);
- model.addAttribute("msg", "验证码不正确!");
- CsrfTokenUtil.refreshToken(request);
- return "login";
- }
- }
- //2、验证用户名和密码
- org.apache.shiro.subject.Subject subject = SecurityUtils.getSubject();
- UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginParam.getUsername(), loginParam.getPassword(), loginParam.isRememberMe());
- String msg = "";
- try {
- subject.login(usernamePasswordToken);
- return "redirect:/";
- } catch (UnknownAccountException e) {
- log.info("UnknownAccountException -- > 账号不存在:");
- msg = "账号不存在!";
- } catch (IncorrectCredentialsException e) {
- log.info("IncorrectCredentialsException -- > 密码不正确:");
- msg = "密码不正确!";
- } catch (LockedAccountException e) {
- log.info("LockedAccountException -- > 账号被锁定");
- msg = "账号被锁定!";
- } catch (Exception e) {
- log.info(e.getMessage());
- }
- model.addAttribute("msg", msg);
- CsrfTokenUtil.refreshToken(request);
- return "login";
- }
3、login.html 添加记住我的复选框
name为之前填的 rememberMe
4、修改 ShiroConfig.java
上面的配置后,当登录后,会创建rememberMe的 cookie,退出浏览器,cookie依然存在。
但是我们访问需要登录(authc)的页面会被拦截到登录页面
解决办法是修改 authc 为 user
我们要修改
- filterChainDefinitionMap.put("/admin/**", "authc");
- filterChainDefinitionMap.put("/user/**", "authc");
为
- filterChainDefinitionMap.put("/admin/**", "user");
- filterChainDefinitionMap.put("/user/**", "user");
然后再次尝试,发现可以访问。
五、效果图如下
首次登录无需验证码,登录错误需要验证码
SpringBoot整合Shiro实现权限控制,验证码的更多相关文章
- SpringBoot整合Shiro实现权限控制
目录 1.SpringBoot整合Shiro 1.1.shiro简介 1.2.代码的具体实现 1.2.1.Maven的配置 1.2.2.整合需要实现的类 1.2.3.项目结构 1.2.4.ShiroC ...
- springboot集成shiro 实现权限控制(转)
shiro apache shiro 是一个轻量级的身份验证与授权框架,与spring security 相比较,简单易用,灵活性高,springboot本身是提供了对security的支持,毕竟是自 ...
- SpringBoot集成Shiro实现权限控制
Shiro简介 Apache Shiro是一个功能强大且易于使用的Java安全框架,用于执行身份验证,授权,加密和会话管理.使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序-从最小的移 ...
- springboot整合shiro进行权限管理
背景:springboot2.1,shiro1.4:由于目前的小项目没做登录,但是客户又需要加上权限,因此楼主就想到了shiro(这是单独的项目,需要集成后台管理系统) shiro简介 Apache ...
- spring-boot整合shiro作权限认证
spring-shiro属于轻量级权限框架,即使spring-security更新换代,市场上大多数企业还是选择shiro 废话不多说 引入pom文件 <!--shiro集成spring--& ...
- shiro系列三、ssm框架整合shiro实现权限控制
shiro权限框架是一个非常优秀的框架,前面的几篇文章对shiro进行了非常详细的介绍和原理分析,那么接下来让我们开始在web项目中使用它(javase也能用shiro): 一.数据库表结构设计 二. ...
- spring-boot整合shiro实现权限管理
1.运行环境 开发工具:intellij idea JDK版本:1.8 项目管理工具:Maven 4.0.0 2.GITHUB地址 https://github.com/nbfujx/springBo ...
- SpringBoot 整合 Shiro 密码登录与邮件验证码登录(多 Realm 认证)
导入依赖(pom.xml) <!--整合Shiro安全框架--> <dependency> <groupId>org.apache.shiro</group ...
- SpringBoot整合Shiro权限框架实战
什么是ACL和RBAC ACL Access Control list:访问控制列表 优点:简单易用,开发便捷 缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理 例子:常见的文件系 ...
随机推荐
- HDU 1789 Doing Homework again(排序,DP)
Doing Homework again Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Oth ...
- C++ bool、三目运算符、引用
bool变量: C++相对于C语言加入了bool变量,其值为true(1) 和 false(0).true表示不为零的数 false表示为零的数,占用一个字节的空间. 代码: /* 编译环境 gc ...
- qt tableview里面添加控件
在QStyledItemDelegate的paint方法里面 void MyItemModelDeletage::paint(QPainter *painter, const QStyleOption ...
- Hack the box: Bastion
介绍 目标:10.10.10.134 (Windows) Kali:10.10.16.65 In conclusion, Bastion is not a medium box. But it wou ...
- vue.js(2)--v-cloak v-text v-html
v-cloak v-text v-html的使用 (1)实例 <!DOCTYPE html> <html lang="en"> <head> ...
- java线程中的同步锁和互斥锁有什么区别?
两者都包括对资源的独占. 区别是 1:互斥是通过竞争对资源的独占使用,彼此没有什么关系,也没有固定的执行顺序. 2:同步是线程通过一定的逻辑顺序占有资源,有一定的合作关系去完成任务.
- JS异常missing ) after argument list
JS异常报错 missing ) after argument list 在使用JS拼接DOM元素时,有这种情况发生,'<a onclick="del(' + data.id + ') ...
- dataTable 、dataView、Dataset 区别
dataTable .dataView.Dataset 区别的经典回答 1.DataView是DataTable的表示,DataTable表里的记录是没有顺序的,但显示可以有不同顺序(DataVIew ...
- valgrind 性能测试工具学习使用
一.valgrind简介 Valgrind工具套件提供了许多调试和分析工具,可帮助您使程序更快,更正确.这些工具中最受欢迎的是Memcheck.它可以检测许多与C和C ++程序中常见的内存相关的错误, ...
- DP+滚动数组 || [Usaco2007 Nov]Telephone Wire 架设电话线 || BZOJ 1705 || Luogu P2885
本来是懒得写题解的…想想还是要勤发题解和学习笔记…然后就滚过来写题解了. 题面:[USACO07NOV]电话线Telephone Wire 题解: F[ i ][ j ] 表示前 i 根电线杆,第 i ...