Shiro的Web流程
Shiro整合SSM
- 准备SSM的配置
- 准备经典五张表(见[Shiro基本使用]),完成测试
准备Shiro的配置
核心过滤器
<!-- 配置Shiro整合web的过滤器--> <filter> <!-- 默认情况下,请求到达这个过滤器,会去Spring容器中名字为filter-name的实例去处理--> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
准备shiroFilter实例
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> ..... </bean>
注入SecurityManager,登录页面路径,过滤器链
<!-- 构建realm--> <bean id="realm" class="com.xxx.realm.ShiroRealm" /> <!-- 构建securityManager--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="realm"/> </bean> <!-- 构建ShiroFilter实例--> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.html" /> <property name="filterChainDefinitionMap"> <map> <entry key="/login.html" value="anon" /> <entry key="/user/**" value="anon" /> <entry key="/**" value="authc" /> </map> </property> </bean>
- 将ShiroRealm的模拟数据库操作,修改为与数据库交互(见[Shiro基本使用]
编写登录功能,并测试效果
@PostMapping("/login") public String login(String username,String password){ // 执行Shiro的认证操作 //1. 直接基于SecurityUtils获取subject主体,不需要手动的将SecurityManager和SecurityUtils手动整合,Spring已经奥丁 Subject subject = SecurityUtils.getSubject(); //2. 发起认证 try { subject.login(new UsernamePasswordToken(username,password)); return "SUCCESS"; } catch (UnknownAccountException exception){ return "username fail!!!"; } catch (IncorrectCredentialsException exception){ return "password fail!!!"; } catch (AuthenticationException e) { return "donot know...!!!"; } }
Shiro整合SpringBoot
- 搭建SpringBoot工程
配置Shiro整合SpringBoot内容
@Configuration public class ShiroConfig { @Bean public DefaultWebSecurityManager securityManager(ShiroRealm realm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); return securityManager; } @Bean public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){ DefaultShiroFilterChainDefinition shiroFilterChainDefinition = new DefaultShiroFilterChainDefinition(); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put("/login.html","anon"); filterChainDefinitionMap.put("/user/**","anon"); filterChainDefinitionMap.put("/**","authc"); shiroFilterChainDefinition.addPathDefinitions(filterChainDefinitionMap); return shiroFilterChainDefinition; } }
Shiro授权方式
过滤器链
public enum DefaultFilter {
// ....
perms(PermissionsAuthorizationFilter.class),
roles(RolesAuthorizationFilter.class),
// ....
}
filterChainDefinitionMap.put("/item/select","roles[超级管理员,运营]");
filterChainDefinitionMap.put("/item/delete","perms[item:delete,item:insert]");
自定义过滤器
仿照RolesAuthorizationFilter实现自定义过滤器
/** * 在要求的多个角色中,有一个满足要求,就放行 * @author zjw * @description */ public class RolesOrAuthorizationFilter extends AuthorizationFilter { @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { // 获取主体subject Subject subject = getSubject(request, response); // 将传入的角色转成数组操作 String[] rolesArray = (String[]) mappedValue; // 健壮性校验 if (rolesArray == null || rolesArray.length == 0) { return true; } // 开始校验 for (String role : rolesArray) { if(subject.hasRole(role)){ return true; } } return false; } }
将自定义过滤器配置给Shiro
@Configuration public class ShiroConfig { @Bean public DefaultWebSecurityManager securityManager(ShiroRealm realm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm); return securityManager; } @Bean public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){ DefaultShiroFilterChainDefinition shiroFilterChainDefinition = new DefaultShiroFilterChainDefinition(); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); filterChainDefinitionMap.put("/login.html","anon"); filterChainDefinitionMap.put("/user/**","anon"); filterChainDefinitionMap.put("/item/select","rolesOr[超级管理员,运营]"); filterChainDefinitionMap.put("/item/delete","perms[item:delete,item:insert]"); filterChainDefinitionMap.put("/**","authc"); shiroFilterChainDefinition.addPathDefinitions(filterChainDefinitionMap); return shiroFilterChainDefinition; } @Value("#{ @environment['shiro.loginUrl'] ?: '/login.jsp' }") protected String loginUrl; @Value("#{ @environment['shiro.successUrl'] ?: '/' }") protected String successUrl; @Value("#{ @environment['shiro.unauthorizedUrl'] ?: null }") protected String unauthorizedUrl;
@Bean
protected ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,ShiroFilterChainDefinition shiroFilterChainDefinition) {
//1. 构建ShiroFilterFactoryBean工厂
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
//2. 设置了大量的路径
filterFactoryBean.setLoginUrl(loginUrl);
filterFactoryBean.setSuccessUrl(successUrl);
filterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
//3. 设置安全管理器
filterFactoryBean.setSecurityManager(securityManager);
//4. 设置过滤器链
filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());
//5. 设置自定义过滤器 , 这里一定要手动的new出来这个自定义过滤器,如果使用Spring管理自定义过滤器,会造成无法获取到Subject
filterFactoryBean.getFilters().put("rolesOr",new RolesOrAuthorizationFilter());
//6. 返回工厂
return filterFactoryBean;
}
}
## 注解
* 注解进行授权时,是基于对Controller类进行代理,在前置增强中对请求进行权限校验
* 因为咱们使用SpringBoot的测试方式,直接在Controller方法上添加注解即可
@GetMapping("/update")
@RequiresRoles(value = {"超级管理员","运营"})
public String update(){
return "item Update!!!";
}
@GetMapping("/insert")
@RequiresRoles(value = {"超级管理员","运营"},logical = Logical.OR)
public String insert(){
return "item Update!!!";
}
// @RequiresPermissions(value = "",logical = Logical.AND)
* 在SpringBoot中注解默认就生效,是因为自动装配中,已经配置好了对注解的支持
@Configuration
@ConditionalOnProperty(name = "shiro.annotations.enabled", matchIfMissing = true)
public class ShiroAnnotationProcessorAutoConfiguration extends AbstractShiroAnnotationProcessorConfiguration {
@Bean
@DependsOn("lifecycleBeanPostProcessor")
@ConditionalOnMissingBean
@Override
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return super.defaultAdvisorAutoProxyCreator();
}
@Bean
@ConditionalOnMissingBean
@Override
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
return super.authorizationAttributeSourceAdvisor(securityManager);
}
}
* 注解的形式无法将错误页面的信息定位到401.html,因为配置的这种路径,只针对过滤器链有效,注解无效。为了实现友好提示的效果,可以配置异常处理器,@RestControllerAdvice,@ControllerAdvice
## 记住我
记住我在开启后,可以针对一些安全级别相对更低的页面采用user过滤器拦截,只要登录过,不需要重新登录就可以访问。
准备两个接口
@GetMapping("/rememberMe")
public String rememberMe(){
return "rememberMe!!!";
}
@GetMapping("/authentication")
public String authentication(){
return "authentication!!!";
}
配置不同的过滤器
filterChainDefinitionMap.put("/item/rememberMe","user");
filterChainDefinitionMap.put("/item/authentication","authc");
在页面追加记住我按钮,并且在登录是,添加rememberMe效果
用户名:
密码:
记住我: