上篇文章介绍了shiro的架构设计,实际使用中我们该怎么使用应用到我们的项目中呢
现在手把手教你如何将shiro应用在实际项目中,首先我们遵循下面这个图
构建项目
新建一个简单的maven项目,引入相关依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.richard.shiro.example</groupId> <artifactId>shiro</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!-- shiro Spring boot 继承starter 本例是web项目,所以引入下面这个, 如果不是web项目项目,官方 <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.5.0-SNAPSHOT</version> </dependency> --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
新建Spring Boot 应用启动
/** * @Description TODO * @Author Richard * @Date 2019/9/17 20:53 **/ @SpringBootApplication public class ShiroExampleApplication { public static void main(String[] args) { SpringApplication.run(ShiroExampleApplication.class, args); } }
定义realm
/** * @Description TODO * @Author Richard * @Date 2019/9/17 21:11 **/ public class CustomAuthenRealm extends AuthorizingRealm { @Autowired private ShiroSampleDao shiroSampleDao; /** * 授权调用实现 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println(" ======== AuthorizationInfo ==== "); String username = (String) super.getAvailablePrincipal(principalCollection); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); Set<String> roles = shiroSampleDao.getRolesByUsername(username); authorizationInfo.setRoles(roles); roles.forEach(role -> { Set<String> permissions = this.shiroSampleDao.getPermissionsByRole(role); authorizationInfo.addStringPermissions(permissions); }); return authorizationInfo; } /** * 登录认证 调用实现 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println(" AuthenticationInfo "); UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); /** * * 在实际应用过程中,你需要再此处,做你的密码安全性校验,以及一些相关的业务处理 */ String password = this.shiroSampleDao.getPasswordByUsername(username); return new SimpleAuthenticationInfo(username, password, getName()); } }
shiro配置
新建shiroConfig 配置类
@Configuration public class ShiroConfig { @Bean public Realm realm() { return new CustomAuthenRealm(); } @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 调用login 接口不需要登录认证 filterChainDefinitionMap.put("/sample/login", "anon"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } // 使用默认的 defaultWebSecurityManager @Bean public DefaultWebSecurityManager defaultWebSecurityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm()); return securityManager; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean @DependsOn("lifecycleBeanPostProcessor") public static DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); creator.setProxyTargetClass(true); return creator; } }
关于自定义的SecurityManager
上面的defaultWebSecurityManager 使用了shiro提供的默认的 SecurityManager,当然如果你的项目特殊 SecurityManager,你也可以继承defaultWebSecurityManager 自定义自己的SecurityManager,举个例子:
public class CustomWebSessionManager extends DefaultWebSessionManager { public static final String LOGIN_TOKEN_KEY = "login-Token"; private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request"; @Override protected Serializable getSessionId(ServletRequest request, ServletResponse response) { String id = WebUtils.toHttp(request).getHeader(LOGIN_TOKEN_KEY); if (!StringUtils.isEmpty(id)) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); return id; } else { return super.getSessionId(request, response); } } }
上面代码是由于我为了增加项目每次请求登录验证的安全性,需要每次请求再请求头上带上一个login-token,为了方便处理,定义自己的 SecurityManager
shiro拦截器与url匹配规则
上面的配置只做了简单的url规则匹配
filterChainDefinitionMap.put("/sample/login", "anon");
下面详细介绍拦截器与url匹配规则
url匹配规则
“?”:匹配一个字符,如”/admin?”,将匹配“ /admin1”、“/admin2”,但不匹配“/admin”“*”:匹配零个或多个字符串,如“/admin*”,将匹配“ /admin”、“/admin123”,但不匹配“/admin/1”(3)“**”:匹配路径中的零个或多个路径,如“/admin/**”,将匹配“/admin/a”、“/admin/a/b”shiro过滤器
shiro 过滤器
anon:匿名过滤器,表示通过了url配置的资源都可以访问,例:“/statics/**=anon”表示statics目录下所有资源都能访问
authc:基于表单的过滤器,表示通过了url配置的资源需要登录验证,否则跳转到登录,例:“/unauthor.jsp=authc”如果用户没有登录访问unauthor.jsp则直接跳转到登录authcBasic:Basic的身份验证过滤器,表示通过了url配置的资源会提示身份验证,例:“/welcom.jsp=authcBasic”访问welcom.jsp时会弹出身份验证框perms:权限过滤器,表示访问通过了url配置的资源会检查相应权限,例:“/statics/**=perms["user:add:*,user:modify:*"]“表示访问statics目录下的资源时只有新增和修改的权限port:端口过滤器,表示会验证通过了url配置的资源的请求的端口号,例:“/port.jsp=port[8088]”访问port.jsp时端口号不是8088会提示错误rest:restful类型过滤器,表示会对通过了url配置的资源进行restful风格检查,例:“/welcom=rest[user:create]”表示通过restful访问welcom资源时只有新增权限roles:角色过滤器,表示访问通过了url配置的资源会检查是否拥有该角色,例:“/welcom.jsp=roles[admin]”表示访问welcom.jsp页面时会检查是否拥有admin角色ssl:ssl过滤器,表示通过了url配置的资源只能通过https协议访问,例:“/welcom.jsp=ssl”表示访问welcom.jsp页面如果请求协议不是https会提示错误user:用户过滤器,表示可以使用登录验证/记住我的方式访问通过了url配置的资源,例:“/welcom.jsp=user”表示访问welcom.jsp页面可以通过登录验证或使用记住我后访问,否则直接跳转到登录logout:退出拦截器,表示执行logout 后,跳转到通过了url配置的资源,例:“/logout.jsp=logout”表示执行了logout 后直接跳转到logout.jsp页面过滤器分类
认证过滤器:anon、authcBasic、auchc、user、logout授权过滤器:perms、roles、ssl、rest、port定义测试用的controller
/** * @Description TODO * @Author Richard * @Date 2019/9/17 21:22 **/ @RestController @RequestMapping("/sample") public class SampleController { @RequestMapping(value = "/login",method = RequestMethod.GET) public String login(HttpServletRequest request, String username, String password) { System.out.println(" username : " + username); System.out.println(" password : " + password); System.out.println(" request query :" + request.getQueryString()); // System.out.println(" request : " + request.getPathInfo()); UsernamePasswordToken token = new UsernamePasswordToken(username, password); token.setRememberMe(true); Subject currentUser = SecurityUtils.getSubject(); try { currentUser.login(token); } catch (UnknownAccountException uae) { System.out.println("登录 用户帐号或密码不正确"); return null; } catch (LockedAccountException lae) { System.out.println("登录 用户帐号已锁定不可用"); return null; } catch (AuthenticationException ae) { System.out.println("登录 认证失败"); return null; } return "login sucess username : " + username + " password : " + password; } @GetMapping("/logout") public void logout() { Subject currentUser = SecurityUtils.getSubject(); currentUser.logout(); } @GetMapping("/read") @RequiresPermissions("sample:read") public String read() { return " I am reading..."; } @GetMapping("/write") @RequiresPermissions("sample:write") public String write() { return " I am writing..."; } }
关于 RequiresPermissions 注解,这是应用权限控制的注解,在shiro有如下几种注解
RequiresAuthentication:
使用该注解标注的类,实例, 在访问或调用时,当前Subject必须在当前session中已经过认证。
RequiresGuest:
使用该注解标注的类,实例, 在访问或调用时,当前Subject可以是“gust”身份,不需要经过认证或者在原先的session中存在记录。
RequiresPermissions:
当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的 。如果当前Subject不具有这样的权限,则 不会被执行。
RequiresRoles:
当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的 。如果当天Subject不同时拥有所有指定角色,则 不会执行还会抛出AuthorizationException异常。
RequiresUser
当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例, 。
在我们权限控制中一般要使用到的是 RequiresPermissions 和 RequiresRoles
好了,经过以上步骤,一个可应用于生产的shiro集成Spring boot就搞定了,如果没有shiro,你需要一整套的登录认证权限控制方案,还要人力去实现,工作量大的很。
在上面的过程中各位看官有没有思考下面这些问题:
1、shiroConfig 各个配置的实例都有什么用,都在整个过程发挥了什么作用?
2、login 做了什么操作,怎么进行代码调用认证?
3、shiro是怎么进行拦截过滤的?
4、单凭几个注解就可以实现权限控制,shiro是怎么做到?
各位看官可以说出你的答案,后面继续剖析这些问题。
例子的代码地址: https://github.com/Chandgaochengdong/shiro