这里使用的是eclipse 开发工具
1.springboot 版本是2.0的,引入了2个shiro 的依赖,如下
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <!-- 安全框架:密码加密、权限控制、身份验证 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <!-- shiro+redis缓存插件 --> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>2.4.2-RELEASE</version> </dependency>
2. shiro 主要分为2部分,第一部分是shiro的配置类,我放在src/main/resources添加config文件夹,新建ShiroConfig。
首先,shiro会配置他的拦截器 shiroFilter(由于swagger和druid的页面也会被拦截,这里要放行,这是个坑),
其次,会配置缓存管理方式,这里我使用的是 shiro-redis (是大神已经写好的redis的自动化管理,自己就不会去写更多的代码了,比如序列化的问题,redis存储问题等)
@Configuration public class ShiroConfig { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; // @Value("${spring.redis.timeout}") // private int timeout; /** * 修复Spring Boot整合shiro出现UnavailableSecurityManagerException 问题 * 此处设置相当于在web.xml中增加filter */ @Bean public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxy() { FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean = new FilterRegistrationBean<DelegatingFilterProxy>(); DelegatingFilterProxy proxy = new DelegatingFilterProxy(); proxy.setTargetFilterLifecycle(true); proxy.setTargetBeanName("shiroFilter"); filterRegistrationBean.setFilter(proxy); return filterRegistrationBean; } /** * SHIRO核心拦截器配置 */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 拦截器.顺序判断 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // authc:所有url都必须认证通过才可以访问 // anon:所有url都都可以匿名访问 filterChainDefinitionMap.put("/user/login", "anon"); // 放行静态资源 filterChainDefinitionMap.put("/static/**", "anon"); // swagger2 放行 filterChainDefinitionMap.put("/swagger-ui.html", "anon"); filterChainDefinitionMap.put("/swagger/**", "anon"); filterChainDefinitionMap.put("/webjars/**", "anon"); filterChainDefinitionMap.put("/swagger-resources/**", "anon"); filterChainDefinitionMap.put("/v2/**", "anon"); filterChainDefinitionMap.put("/docs/**", "anon"); // druid 监控放行 filterChainDefinitionMap.put("/druid/**", "anon"); // 退出 过滤器 filterChainDefinitionMap.put("/user/logout", "logout"); // 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 // filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); // 配置退出过滤器,如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/login.html"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/index.html"); // 未授权要跳转的链接; shiroFilterFactoryBean.setUnauthorizedUrl("/login.html"); return shiroFilterFactoryBean; } /** * securityManager 安全管理器 (通过 authorize 调用 自定义 realm 数据进行认证和授权) */ @Bean(name = "securityManager") public SecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm. securityManager.setRealm(myRealm); // 自定义缓存实现 使用redis securityManager.setCacheManager(cacheManager()); // 自定义session管理 使用redis securityManager.setSessionManager(sessionManager()); return securityManager; } /** * 自定义realm; (账号密码校验;权限等) * * @return */ @Bean(name = "myRealm") public MyRealm myRealm() { MyRealm myRealm = new MyRealm(); // 添加 matcher加密算法到 MyRealm myRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return myRealm; } /** * 密码匹配凭证管理器 * * @return */ @Bean(name = "hashedCredentialsMatcher") public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); // 采用MD5方式加密 hashedCredentialsMatcher.setHashAlgorithmName("MD5"); // 设置加密次数 hashedCredentialsMatcher.setHashIterations(1024); return hashedCredentialsMatcher; } /** * Shiro生命周期处理器 * * @return */ @Bean(name = "lifecycleBeanPostProcessor") public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions 授权注解) * 需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 * * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor( @Qualifier("securityManager") SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } /** * 配置shiro redisManager 使用的是shiro-redis开源插件 * * @return */ public RedisManager redisManager() { RedisManager redisManager = new RedisManager(); redisManager.setHost(host); redisManager.setPort(port); redisManager.setExpire(1800);// 配置缓存过期时间 //redisManager.setTimeout(timeout); // redisManager.setPassword(password); return redisManager; } /** * cacheManager 缓存 redis实现 使用的是shiro-redis开源插件 * * @return */ public RedisCacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager()); return redisCacheManager; } /** * RedisSessionDAO shiro sessionDao层的实现 通过redis 使用的是shiro-redis开源插件 */ @Bean public RedisSessionDAO redisSessionDAO() { RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager()); return redisSessionDAO; } /** * shiro session的管理 */ @Bean public DefaultWebSessionManager sessionManager() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionDAO(redisSessionDAO()); return sessionManager; } /*@Bean(name = "redisTemplate") public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, String> template = new RedisTemplate<>(); RedisSerializer<String> redisSerializer = new StringRedisSerializer(); template.setConnectionFactory(factory); // key序列化方式 template.setKeySerializer(redisSerializer); // value序列化 template.setValueSerializer(redisSerializer); // value hashmap序列化 template.setHashValueSerializer(redisSerializer); // key haspmap序列化 template.setHashKeySerializer(redisSerializer); // return template; }*/ }
3.shiro 的另一个重要部分是realm的配置,其中包含2个方面,一是权限的自定义设定,二是登入认证的自定义设定,我放在src/main/resources添加realm文件夹,新建MyRealm。由于业务的原因,我这里只做了登入的认证,即加密算法MD5的认证
public class MyRealm extends AuthorizingRealm { @Autowired private UserService userService; /** * 在认证、授权内部实现机制中都有提到,最终处理都将交给Real进行处理。因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO. * Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。 * 该方法主要执行以下操作: * * 检查提交的进行认证的令牌信息 根据令牌信息从数据源(通常为数据库)中获取用户信息 对用户信息进行匹配验证。 * 验证通过将返回一个封装了用户信息的AuthenticationInfo实例。 * 验证失败则抛出AuthenticationException异常信息。而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo(),重写获取用户信息的方法。 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 1.从主体传过来的认证信息中,获取用户名,密码 String account = (String) token.getPrincipal(); // String password = new String((char[]) token.getCredentials()); // 2.通过用户名去到数据库中获取凭证 AdminUser adminUser = this.userService.findUserByAccount(account); // 判断用户是否存在 if (adminUser == null) { throw new UnknownAccountException(); } // 判断用户是否有效 if (adminUser.getAdminIsenable() == 0) { throw new LockedAccountException(); } // 3.加密:MD5+salt,在这里添加盐值,以用户名作为盐值 ByteSource salt = ByteSource.Util.bytes(adminUser.getAdminName()); String realName = this.getName(); // 4.验证凭证信息(密码信息) SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(account, adminUser.getAdminPassword(), salt, realName); // 5.当shiro验证成功,把用户信息放在session里 Session session = SecurityUtils.getSubject().getSession(true); session.setAttribute("activeUser", adminUser.getAdminAccount()); return authenticationInfo; } /** * 授权用户权限 授权的方法是在碰到<shiro:hasPermission name=''></shiro:hasPermission>标签的时候调用的 * ,它会去检测shiro框架中的权限(这里的permissions)是否包含有该标签的name值,如果有,里面的内容显示 * ,如果没有,里面的内容不予显示(这就完成了对于权限的认证.) * * shiro的权限授权是通过继承AuthorizingRealm抽象类,重载doGetAuthorizationInfo(); * 当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行,所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。 * 在这个方法中主要是使用类:SimpleAuthorizationInfo 进行角色的添加和权限的添加。 * authorizationInfo.addRole(role.getRole()); * authorizationInfo.addStringPermission(p.getPermission()); * 当然也可以添加set集合:roles是从数据库查询的当前用户的角色,stringPermissions是从数据库查询的当前用户对应的权限 * authorizationInfo.setRoles(roles); * authorizationInfo.setStringPermissions(stringPermissions); * 就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “perms[权限添加]”); * 就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问, * 如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, * “roles[100002],perms[权限添加]”); * 就说明访问/add这个链接必须要有“权限添加”这个权限和具有“100002”这个角色才可以访问。 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 1.从主体传过来的认证信息中,获取用户名 // String acoount = (String) principals.getPrimaryPrincipal(); // 查询用户名称 // User user = userService.findUserByAccount(acoount); // 添加角色和权限 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); // for (Role role:user.getRoles()) { // //添加角色 // simpleAuthorizationInfo.addRole(role.getRoleName()); // for (Permission permission:role.getPermissions()) { // //添加权限 // simpleAuthorizationInfo.addStringPermission(permission.getPermission()); // } // } return simpleAuthorizationInfo; } }
4. 最后是在controller中,怎么调用这个认证,返回类型是我自己封装的一个类型,可以自己定义。这里需要注意的是,要去新建subject实例,才能去调用login验证。
public Result<?> login(@RequestParam(value = "account") String account, @RequestParam("password") String password) { // 结果信息 Result<?> result = new Result<>(); // 创建Subject实例 Subject subject = SecurityUtils.getSubject(); // 将用户名及密码封装到UsernamePasswordToken UsernamePasswordToken token = new UsernamePasswordToken(account, password); try { // 完成登录验证 subject.login(token); // 判断当前用户是否验证成功 if (subject.isAuthenticated() == true) { // 登入成功 ResultEnum resultEnum = ResultEnum.AdminSuccess; result = ResultUtil.getSuccess(resultEnum.getCode(), resultEnum.getMsg(),""); // 打印到控制台 LogUtil.printLog(result.getMsg()+",登入用户: " + account); } } catch (LockedAccountException e) { // 用户无效 throw new MyRuntimeException(ResultEnum.LockedAccountException); } catch (IncorrectCredentialsException e) { // 密码不正确 throw new MyRuntimeException(ResultEnum.IncorrectCredentialsException); } catch (AuthenticationException e) { // 用户不存在 throw new MyRuntimeException(ResultEnum.UnknownAccountException); } catch (Exception e) { throw e; } return result; }