项目版本:
springboot2.x
shiro:1.3.2
Maven配置:
java
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency>
写在前面的话:
springboot中集成shiro相对简单,只须要两个类:一个是shiroConfig类,一个是CustonRealm类。web
ShiroConfig类:
顾名思义就是对shiro的一些配置,相对于以前的xml配置。包括:过滤的文件和权限,密码加密的算法,其用注解等相关功能。算法
CustomRealm类:
自定义的CustomRealm继承AuthorizingRealm。而且重写父类中的doGetAuthorizationInfo(权限相关)、doGetAuthenticationInfo(身份认证)这两个方法。最基本的配置:
shiroConfig配置:spring
package com.cj.shirodemo.config; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; 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.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import java.util.LinkedHashMap; import java.util.Map; /** * 描述: * * @author caojing * @create 2019-01-27-13:38 */ @Configuration public class ShiroConfig { @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/login"); shiroFilterFactoryBean.setUnauthorizedUrl("/notRole"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // <!-- authc:全部url都必须认证经过才能够访问; anon:全部url都均可以匿名访问--> filterChainDefinitionMap.put("/webjars/**", "anon"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/", "anon"); filterChainDefinitionMap.put("/front/**", "anon"); filterChainDefinitionMap.put("/api/**", "anon"); filterChainDefinitionMap.put("/admin/**", "authc"); filterChainDefinitionMap.put("/user/**", "authc"); //主要这行代码必须放在全部权限设置的最后,否则会致使全部 url 都被拦截 剩余的都须要认证 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager(); defaultSecurityManager.setRealm(customRealm()); return defaultSecurityManager; } @Bean public CustomRealm customRealm() { CustomRealm customRealm = new CustomRealm(); return customRealm; } }
shiroConfig 也不复杂,基本就三个方法。再说这三个方法以前,我想给你们说一下shiro的三个核心概念:数据库
Subject: 表明当前正在执行操做的用户,但Subject表明的能够是人,也能够是任何第三方系统账号。固然每一个subject实例都会被绑定到SercurityManger上。
SecurityManger:SecurityManager是Shiro核心,主要协调Shiro内部的各类安全组件,这个咱们不须要太关注,只须要知道能够设置自定的Realm。
Realm:用户数据和Shiro数据交互的桥梁。好比须要用户身份认证、权限认证。都是须要经过Realm来读取数据。
shiroFilter方法:
这个方法看名字就知道了:shiro的过滤器,能够设置登陆页面(setLoginUrl)、权限不足跳转页面(setUnauthorizedUrl)、具体某些页面的权限控制或者身份认证。
注意:这里是须要设置SecurityManager(setSecurityManager)。
默认的过滤器还有:anno、authc、authcBasic、logout、noSessionCreation、perms、port、rest、roles、ssl、user过滤器。
具体的你们能够查看package org.apache.shiro.web.filter.mgt.DefaultFilter。这个类,经常使用的也就authc、anno。
securityManager 方法:
查看源码能够知道 securityManager是一个接口类,咱们能够看下它的实现类:apache
具体怎么实现的,感兴趣的同窗能够看下。因为项目是一个web项目,因此咱们使用的是DefaultWebSecurityManager ,而后设置本身的Realm。
CustomRealm 方法:
将 customRealm的实例化交给spring去管理,固然这里也能够利用注解的方式去注入。customRealm配置:api
package com.cj.shirodemo.config; import org.apache.shiro.SecurityUtils; 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 org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import java.util.HashSet; import java.util.Set; /** * 描述: * * @author caojing * @create 2019-01-27-13:57 */ public class CustomRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String username = (String) SecurityUtils.getSubject().getPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set<String> stringSet = new HashSet<>(); stringSet.add("user:show"); stringSet.add("user:admin"); info.setStringPermissions(stringSet); return info; } /** * 这里能够注入userService,为了方便演示,我就写死了账号了密码 * private UserService userService; * <p> * 获取即将须要认证的信息 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("-------身份认证方法--------"); String userName = (String) authenticationToken.getPrincipal(); String userPwd = new String((char[]) authenticationToken.getCredentials()); //根据用户名从数据库获取密码 String password = "123"; if (userName == null) { throw new AccountException("用户名不正确"); } else if (!userPwd.equals(password )) { throw new AccountException("密码不正确"); } return new SimpleAuthenticationInfo(userName, password,getName()); } }
说明:
自定义的Realm类继承AuthorizingRealm类,而且重载doGetAuthorizationInfo和doGetAuthenticationInfo两个方法。
doGetAuthorizationInfo: 权限认证,即登陆事后,每一个身份不必定,对应的所能看的页面也不同。
doGetAuthenticationInfo:身份认证。即登陆经过帐号和密码验证登录人的身份信息。安全
controller类:
新建一个HomeIndexController类,加入以下代码: springboot
@RequestMapping(value = "/login", method = RequestMethod.GET) @ResponseBody public String defaultLogin() { return "首页"; } @RequestMapping(value = "/login", method = RequestMethod.POST) @ResponseBody public String login(@RequestParam("username") String username, @RequestParam("password") String password) { // 从SecurityUtils里边建立一个 subject Subject subject = SecurityUtils.getSubject(); // 在认证提交前准备 token(令牌) UsernamePasswordToken token = new UsernamePasswordToken(username, password); // 执行认证登录 try { subject.login(token); } catch (UnknownAccountException uae) { return "未知帐户"; } catch (IncorrectCredentialsException ice) { return "密码不正确"; } catch (LockedAccountException lae) { return "帐户已锁定"; } catch (ExcessiveAttemptsException eae) { return "用户名或密码错误次数过多"; } catch (AuthenticationException ae) { return "用户名或密码不正确!"; } if (subject.isAuthenticated()) { return "登陆成功"; } else { token.clear(); return "登陆失败"; } }
测试:
咱们可使用postman进行测试:app
ok 身份认证是没问题了,咱们再来考虑如何加入权限。
利用注解配置权限:
其实,咱们彻底能够不用注解的形式去配置权限,由于在以前已经加过了:DefaultFilter类中有perms(相似于perms[user:add])这种形式的。可是试想一下,这种控制的粒度可能会很细,具体到某一个类中的方法,那么若是是配置文件配,是否是每一个方法都要加一个perms?可是注解就不同了,直接写在方法上面,简单快捷。
很简单,主须要在shiroConfig类中再加入以下代码,就能开启注解:
@Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * * * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 * * * 配置如下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)便可实现此功能 * * @return */ @Bean @DependsOn({"lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; }
新建一个UserController类。以下:
@RequestMapping("/user") @Controller public class UserController { @RequiresPermissions("user:list") @ResponseBody @RequestMapping("/show") public String showUser() { return "这是学生信息"; } }
重复刚才的登陆步骤,登陆成功后,postman 输入localhost:8080/user/show
确实是没有权限。方法上是 @RequiresPermissions("user:list"),而customRealm中是 user:show、user:admin。咱们能够调整下方法上的权限改成user:show。调试一下,发现成功了。
这里有一个问题:当没有权限时,系统会报错,而没有跳转到对应的没有权限的页面,也就是setUnauthorizedUrl这个方法没起做用,这个问题,下一篇会给出解决方案-。-
密码采用加密方式进行验证:
其实上面的功能已经基本知足咱们的需求了,可是惟一一点美中不足的是,密码都是采用的明文方式进行比对的。那么shiro是否提供给咱们一种密码加密的方式呢?答案是确定。
shiroConfig中加入加密配置:
@Bean(name = "credentialsMatcher") public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); // 散列算法:这里使用MD5算法; hashedCredentialsMatcher.setHashAlgorithmName("md5"); // 散列的次数,好比散列两次,至关于 md5(md5("")); hashedCredentialsMatcher.setHashIterations(2); // storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码 hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true); return hashedCredentialsMatcher; }
customRealm初始化的时候耶须要作一些改变:
@Bean public CustomRealm customRealm() { CustomRealm customRealm = new CustomRealm(); // 告诉realm,使用credentialsMatcher加密算法类来验证密文 customRealm.setCredentialsMatcher(hashedCredentialsMatcher()); customRealm.setCachingEnabled(false); return customRealm; }
流程是这样的,用户注册的时候,程序将明文经过加密方式加密,存到数据库的是密文,登陆时将密文取出来,再经过shiro将用户输入的密码进行加密对比,同样则成功,不同则失败。
咱们能够看到这里的加密采用的是MD5,并且是加密两次(MD5(MD5))。
shiro提供了SimpleHash类帮助咱们快速加密:
public static String MD5Pwd(String username, String pwd) { // 加密算法MD5 // salt盐 username + salt // 迭代次数 String md5Pwd = new SimpleHash("MD5", pwd, ByteSource.Util.bytes(username + "salt"), 2).toHex(); return md5Pwd; }
也就是说注册的时候调用一下上面的方法获得密文以后,再存入数据库。
在CustomRealm进行身份认证的时候咱们也须要做出改变:
System.out.println("-------身份认证方法--------"); String userName = (String) authenticationToken.getPrincipal(); String userPwd = new String((char[]) authenticationToken.getCredentials()); //根据用户名从数据库获取密码 String password = "2415b95d3203ac901e287b76fcef640b"; if (userName == null) { throw new AccountException("用户名不正确"); } else if (!userPwd.equals(userPwd)) { throw new AccountException("密码不正确"); } //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配 return new SimpleAuthenticationInfo(userName, password, ByteSource.Util.bytes(userName + "salt"), getName());
这里惟一须要注意的是:你注册的加密方式和设置的加密方式还有Realm中身份认证的方式都是要如出一辙的。 本文中的加密 :MD5两次、salt=username+salt加密。