Shiro整合springboot以及自定义Realm
Posted beFuckingRich
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shiro整合springboot以及自定义Realm相关的知识,希望对你有一定的参考价值。
二、SpringBoot应用整合Shiro
-
JavaSE应用中使用
-
web应用中使用
-
SSM整合Shiro(配置多,用的少)
-
SpringBoot应用整合Shiro
-
2.1 创建SpringBoot应用
-
lombok
-
spring web
-
thymeleaf
2.2 整合Druid和MyBatis
-
依赖
-
<!-- druid starter --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <!--mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency>
- 配置
-
spring: datasource: druid: url: jdbc:mysql://47.96.11.185:3306/test # MySQL如果是8.x com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.jdbc.Driver username: root password: admin123 initial-size: 1 min-idle: 1 max-active: 20 mybatis: mapper-locations: classpath:mappers/*Mapper.xml type-aliases-package: com.qfedu.springbootssm.beans
-
2.3 整合Shiro
-
-
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency>
-
-
@Configuration public class ShiroConfig { @Bean public JdbcRealm getJdbcRealm(DataSource dataSource){ JdbcRealm jdbcRealm = new JdbcRealm(); //JdbcRealm会自行从数据库查询用户及权限数据(数据库的表结构要符合JdbcRealm的规范) jdbcRealm.setDataSource(dataSource); //JdbcRealm默认开启认证功能,需要手动开启授权功能 jdbcRealm.setPermissionsLookupEnabled(true); return jdbcRealm; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(JdbcRealm jdbcRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(jdbcRealm); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean(); //过滤器就是shiro就行权限校验的核心,进行认证和授权是需要SecurityManager的 filter.setSecurityManager(securityManager); Map<String,String> filterMap = new HashMap<>(); filterMap.put("/","anon"); filterMap.put("/login.html","anon"); filterMap.put("/regist.html","anon"); filterMap.put("/user/login","anon"); filterMap.put("/user/regist","anon"); filterMap.put("/static/**","anon"); filterMap.put("/**","authc"); filter.setFilterChainDefinitionMap(filterMap); filter.setLoginUrl("/login.html"); //设置未授权访问的页面路径 filter.setUnauthorizedUrl("/login.html"); return filter; } }
-
@Service public class UserServiceImpl { public void checkLogin(String userName,String userPwd) throws Exception{ Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd); subject.login(token); } }
UserController.java
-
@Controller @RequestMapping("user") public class UserController { @Resource private UserServiceImpl userService; @RequestMapping("login") public String login(String userName,String userPwd){ try { userService.checkLogin(userName,userPwd); System.out.println("------登录成功!"); return "index"; } catch (Exception e) { System.out.println("------登录失败!"); return "login"; } } }
-
- 自定义Realm
- 自定义Realm
- 自定义Real
- 自定义Realm
/** * 1.创建一个类继承AuthorizingRealm类(实现了Realm接口的类) * 2.重写doGetAuthorizationInfo和doGetAuthenticationInfo方法 * 3.重写getName方法返回当前realm的一个自定义名称 */ public class MyRealm extends AuthorizingRealm { @Resource private UserDAO userDAO; @Resource private RoleDAO roleDAO; @Resource private PermissionDAO permissionDAO; public String getName() { return "myRealm"; } /** * 获取授权数据(将当前用户的角色及权限信息查询出来) */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //获取用户的用户名 String username = (String) principalCollection.iterator().next(); //根据用户名查询当前用户的角色列表 Set<String> roleNames = roleDAO.queryRoleNamesByUsername(username); //根据用户名查询当前用户的权限列表 Set<String> ps = permissionDAO.queryPermissionsByUsername(username); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(roleNames); info.setStringPermissions(ps); return info; } /** * 获取认证的安全数据(从数据库查询的用户的正确数据) */ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //参数authenticationToken就是传递的 subject.login(token) // 从token中获取用户名 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); //根据用户名,从数据库查询当前用户的安全数据 User user = userDAO.queryUserByUsername(username); AuthenticationInfo info = new SimpleAuthenticationInfo( username, //当前用户用户名 user.getUserPwd(), //从数据库查询出来的安全密码 getName()); return info; } }
- 自定义Realm
-
二、加密
-
明文-----(加密规则)-----密文
-
加密规则可以自定义,在项目开发中我们通常使用BASE64和MD5编码方式
-
BASE64:可反编码的编码方式(对称)
-
明文----密文
-
密文----明文
-
-
MD5: 不可逆的编码方式(非对称)
-
明文----密文
-
-
-
如果数据库用户的密码存储的密文,Shiro该如何完成验证呢?
-
使用Shiro提供的加密功能,对输入的密码进行加密之后再进行认证。
2.1 加密介绍
-
-
-
2.2 Shiro使用加密认证
-
-
@Configuration public class ShiroConfig { //... @Bean public HashedCredentialsMatcher getHashedCredentialsMatcher(){ HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); //matcher就是用来指定加密规则 //加密方式 matcher.setHashAlgorithmName("md5"); //hash次数 matcher.setHashIterations(1); //此处的循环次数要与用户注册是密码加密次数一致 return matcher; } //自定义Realm @Bean public MyRealm getMyRealm( HashedCredentialsMatcher matcher ){ MyRealm myRealm = new MyRealm(); myRealm.setCredentialsMatcher(matcher); return myRealm; } //... }
2.3 用户注册密码加密处理
-
-
<form action="/user/regist" method="post"> <p>帐号:<input type="text" name="userName"/></p> <p>密码:<input type="text" name="userPwd"/></p> <p><input type="submit" value="提交注册"/></p> </form>
UserController
-
@Controller @RequestMapping("user") public class UserController { @Resource private UserServiceImpl userService; @RequestMapping("/regist") public String regist(String userName,String userPwd) { System.out.println("------注册"); //注册的时候要对密码进行加密存储 Md5Hash md5Hash = new Md5Hash(userPwd); System.out.println("--->>>"+ md5Hash.toHex()); //加盐加密 int num = new Random().nextInt(90000)+10000; //10000—99999 String salt = num+""; Md5Hash md5Hash2 = new Md5Hash(userPwd,salt); System.out.println("--->>>"+md5Hash2); //加盐加密+多次hash Md5Hash md5Hash3 = new Md5Hash(userPwd,salt,3); System.out.println("--->>>"+md5Hash3); //SimpleHash hash = new SimpleHash("md5",userPwd,num,3); //将用户信息保存到数据库时,保存加密后的密码,如果生成的随机盐,盐也要保存 return "login"; } }
2.4 如果密码进行了加盐处理,则Realm在返回认证数据时需要返回盐
-
在自定义Realm中:
-
-
-
public class MyRealm extends AuthorizingRealm { @Resource private UserDAO userDAO; @Resource private RoleDAO roleDAO; @Resource private PermissionDAO permissionDAO; public String getName() { return "myRealm"; } /** * 获取认证的安全数据(从数据库查询的用户的正确数据) */ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //参数authenticationToken就是传递的 subject.login(token) // 从token中获取用户名 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); //根据用户名,从数据库查询当前用户的安全数据 User user = userDAO.queryUserByUsername(username); // AuthenticationInfo info = new SimpleAuthenticationInfo( // username, //当前用户用户名 // user.getUserPwd(), //从数据库查询出来的安全密码 // getName()); //如果数据库中用户的密码是加了盐的 AuthenticationInfo info = new SimpleAuthenticationInfo( username, //当前用户用户名 user.getUserPwd(), //从数据库查询出来的安全密码 ByteSource.Util.bytes(user.getPwdSalt()), getName()); return info; } }
三、退出登录
-
-
filterMap.put("/exit","logout");
在页面的“退出”按钮上,跳转到logout对应的url
-
-
<a href="exit">退出</a>
四、授权
用户登录成功之后,要进行响应的操作就需要有对应的权限;在进行操作之前对权限进行检查—授权
权限控制通常有两类做法:
-
不同身份的用户登录,我们现在不同的操作菜单(没有权限的菜单不现实)
-
对所有用户显示所有菜单,当用户点击菜单以后再验证当前用户是否有此权限,如果没有则提示权限不足
4.1 HTML授权
-
在菜单页面只显示当前用户拥有权限操作的菜单
-
shiro标签
-
-
<shiro:hasPermission name="sys:c:save"> <dd><a href="javascript:;">入库</a></dd> </shiro:hasPermission>
4.2 过滤器授权
-
-
filterMap.put("/c_add.html","perms[sys:c:save]"); //设置未授权访问的页面路径—当权限不足时显示此页面 filter.setUnauthorizedUrl("/lesspermission.html");
-
-
4.3 注解授权
-
配置Spring对Shiro注解的支持:ShiroConfig.java
-
@Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); autoProxyCreator.setProxyTargetClass(true); return autoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor( DefaultWebSecurityManager securityManager){ AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; }
- 在请求的控制器添加权限注解
-
@Controller @RequestMapping("customer") public class CustomerController { @RequestMapping("list") //如果没有 sys:k:find 权限,则不允许执行此方法 @RequiresPermissions("sys:k:find") // @RequiresRoles("") public String list(){ System.out.println("----------->查询客户信息"); return "customer_list"; } }
- 通过全局异常处理,指定权限不足时的页面跳转
-
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler public String doException(Exception e){ if(e instanceof AuthorizationException){ return "lesspermission"; } return null; } }
4.4 手动授权
-
在代码中进行手动的权限校验
-
Subject subject = SecurityUtils.getSubject(); if(subject.isPermitted("sys:k:find")){ System.out.println("----------->查询客户信息"); return "customer_list"; }else{ return "lesspermission"; }
-
-
-
-
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.4.0</version> </dependency>
5.2 配置缓存策略
-
-
<?xml version="1.0" encoding="UTF-8"?> <ehcache updateCheck="false" dynamicConfig="false"> <diskStore path="C:\\TEMP" /> <cache name="users" timeToLiveSeconds="300" maxEntriesLocalHeap="1000"/> <defaultCache name="defaultCache" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" maxElementsOnDisk="100000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"/> <!--缓存淘汰策略:当缓存空间比较紧张时,我们要存储新的数据进来,就必然要删除一些老的数据 LRU 最近最少使用 FIFO 先进先出 LFU 最少使用 --> </ehcache>
5.3 加入缓存管理
-
-
@Bean public EhCacheManager getEhCacheManager(){ EhCacheManager ehCacheManager = new EhCacheManager(); ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml"); return ehCacheManager; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myRealm); securityManager.setCacheManager(getEhCacheManager()); return securityManager; }
-
-
-
- Shiro进行认证和授权是基于session实现的,Shiro包含了对session的管理
-
-
自定义session管理器
-
将自定义的session管理器设置给SecurityManager
-
-
@Bean public DefaultWebSessionManager getDefaultWebSessionManager(){ DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); System.out.println("----------"+sessionManager.getGlobalSessionTimeout()); // 1800000 //配置sessionManager sessionManager.setGlobalSessionTimeout(5*60*1000); return sessionManager; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myRealm); securityManager.setCacheManager(getEhCacheManager()); securityManager.setSessionManager(getDefaultWebSessionManager()); return securityManager; }
-
-
-
-
未认证—可访问的页面—(陌生人)—问候
-
login.html、regist.html
-
记住我—可访问的页面—(前女友)—朋友间的拥抱
-
info.html
-
已认证—可访问的页面—(现女友)—牵手
-
-
-
-
// anon 表示未认证可访问的url // user 表示记住我可访问的url(已认证也可以访问) //authc 表示已认证可访问的url //perms 表示必须具备指定的权限才可访问 //logout 表示指定退出的url filterMap.put("/","anon"); filterMap.put("/index.html","user"); filterMap.put("/login.html","anon"); filterMap.put("/regist.html","anon"); filterMap.put("/user/login","anon"); filterMap.put("/user/regist","anon"); filterMap.put("/layui/**","anon"); filterMap.put("/**","authc"); filterMap.put("/c_add.html","perms[sys:c:save]"); filterMap.put("/exit","logout");
-
-
@Bean public CookieRememberMeManager cookieRememberMeManager(){ CookieRememberMeManager rememberMeManager = new CookieRememberMeManager(); //cookie必须设置name SimpleCookie cookie = new SimpleCookie("rememberMe"); cookie.setMaxAge(30*24*60*60); rememberMeManager.setCookie(cookie); return rememberMeManager; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myRealm); securityManager.setCacheManager(getEhCacheManager()); securityManager.setSessionManager(getDefaultWebSessionManager()); //设置remember管理器 securityManager.setRememberMeManager(cookieRememberMeManager()); return securityManager; }
-
2.3 登录认证时设置token“记住我”
-
-
<form action="/user/login" method="post"> <p>帐号:<input type="text" name="userName"/></p> <p>密码:<input type="text" name="userPwd"/></p> <p>记住我:<input type="checkbox" name="rememberMe"/></p> <p><input type="submit" value="登录"/></p> </form>
- 控制器
-
@Controller @RequestMapping("user") public class UserController { @Resource private UserServiceImpl userService; @RequestMapping("login") public String login(String userName,String userPwd,boolean rememberMe){ try { userService.checkLogin(userName,userPwd,rememberMe); System.out.println("------登录成功!"); return "index"; } catch (Exception e) { System.out.
以上是关于Shiro整合springboot以及自定义Realm的主要内容,如果未能解决你的问题,请参考以下文章
全栈编程系列SpringBoot整合Shiro(含KickoutSessionControlFilter并发在线人数控制以及不生效问题配置启动异常No SecurityManager...)
全栈编程系列SpringBoot整合Shiro(含KickoutSessionControlFilter并发在线人数控制以及不生效问题配置启动异常No SecurityManager...)(代码片段
-