添加依赖
<!-- 导入thymeleaf依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- shiro与spring整合依赖 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> <scope>compile</scope> </dependency>
ShiroConfig类
指定哪些路径可以直接访问,哪些路径需要登录认证后才能访问
package cn.tedu.springbootshiro02.shiro; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { /** * 创建ShiroFilterFactoryBean */ @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //设置安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); /** * Shiro内置过滤器,可以实现权限相关的拦截器 * 常用的过滤器: * anon: 无需认证(登录)可以访问 * authc: 必须认证才可以访问 * user: 如果使用rememberMe的功能可以直接访问 * perms: 该资源必须得到资源权限才可以访问 * role: 该资源必须得到角色权限才可以访问 */ Map<String,String> filterMap = new LinkedHashMap<String,String>(); /**设置是controller中的那些路径需要登录认证,哪些不需要登录认证*/ filterMap.put("/index", "anon");//这个路径放行 filterMap.put("/login", "anon");//这个路径放行 filterMap.put("/add", "authc");//授权的页面必须单独拎出来,设置需要登录验证,否则授权无效 filterMap.put("/update", "authc");//授权的页面必须单独拎出来,设置需要登录验证,否则授权无效 filterMap.put("/**", "authc");//其他页面需要登录才能访问 //设置需要登录认证才能访问的页面,但未登录直接访问时的重定向请求 shiroFilterFactoryBean.setLoginUrl("/toLogin");//对应的controller中有这个 @RequestMapping("/toLogin") /**设置授权拦截,访问controller中的/add路径,需要用户添加的权限,访问/update路径,需要用户更新的权限; 授权的具体逻辑在UserRealm类的doGetAuthorizationInfo方法中*/ filterMap.put("/add", "perms[add]");//[]中的字符串要跟数据库中perms字段存的字符串保持一致 filterMap.put("/update", "perms[update]");//[]中的字符串要跟数据库中perms字段存的字符串保持一致 //设置未授权重定向请求 shiroFilterFactoryBean.setUnauthorizedUrl("/unAuth"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); return shiroFilterFactoryBean; } /** * 创建DefaultWebSecurityManager */ @Bean(name="securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //关联realm securityManager.setRealm(userRealm); return securityManager; } /** * 创建Realm */ @Bean(name="userRealm") public UserRealm getRealm(){ return new UserRealm(); } /** * 配置ShiroDialect,用户thymeleaf和shiro标签配合使用 */ @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); } }
UserRealm类
doGetAuthenticationInfo方法是具体的执行认证逻辑(与数据库中的数据比对校验)
package cn.tedu.springbootshiro02.shiro; import cn.tedu.springbootshiro02.entity.User; import cn.tedu.springbootshiro02.service.UserService; 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.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; public class UserRealm extends AuthorizingRealm { @Autowired UserService userService; /** * 执行授权逻辑 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行授权逻辑"); //给当前登录用户 资源授权,授权字符串要跟filterMap.put("/add", "perms[add]");中[]字符串一致 SimpleAuthorizationInfo sai = new SimpleAuthorizationInfo(); // 获取到当前登录用户 subject Subject subject = SecurityUtils.getSubject(); //这个principal就是下面登录认证逻辑中return new SimpleAuthenticationInfo(user,user.getPassword(),"");第一个参数user User user = (User) subject.getPrincipal(); // 根据用户名查询数据库 User dbUser = userService.findByName(user.getName()); // 根据数据库查询到的权限信息,赋予该用户权限 sai.addStringPermission(dbUser.getPerms()); return sai; } /** * 执行认证(登录)逻辑 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行认证(登录)逻辑"); //编写shiro判断逻辑,判断用户名和密码 //1.判断用户名 UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken; User user = userService.findByName(token.getUsername()); //判断用户输入的用户名跟数据库的用户名是否一致 if(user==null){ //用户名不存在 return null;//shiro底层会抛出UnKnowAccountException } //2.判断密码 return new SimpleAuthenticationInfo(user,user.getPassword(),""); } }
登录认证
controller写法
@RequestMapping("/login") public String login(String name,String password,Model model){ /** * 使用Shiro编写认证操作 */ //1.获取Subject Subject subject = SecurityUtils.getSubject(); //2.封装用户数据 UsernamePasswordToken token = new UsernamePasswordToken(name,password); //3.执行登录方法 try { subject.login(token);//将token传给UserRealm类作为doGetAuthenticationInfo方法的参数进行登录验证 //登录成功 return "redirect:/index";//重定向到controller请求/index地址 } catch (UnknownAccountException e) {//登录失败:用户名不存在 model.addAttribute("msg", "用户名不存在"); return "login";//转发到到templates目录下对应的html页面 }catch (IncorrectCredentialsException e) {//登录失败:密码错误 model.addAttribute("msg", "密码错误"); return "login"; } }
UserRealm中认证逻辑
/** * 执行认证(登录)逻辑 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行认证(登录)逻辑"); //编写shiro判断逻辑,判断用户名和密码 //1.判断用户名 UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;//就是controller中 登录时传过来的token:subject.login(token); token中携带用户名和密码 User user = userService.findByName(token.getUsername()); //判断用户输入的用户名跟数据库的用户名是否一致 if(user==null){ //用户名不存在 return null;//shiro底层会抛出UnKnowAccountException } //2.判断密码 return new SimpleAuthenticationInfo(user,user.getPassword(),""); }
授权
1、在ShiroConfig类中指定哪些路径需要什么样的权限才能访问
filterMap.put("/add", "authc");//授权的页面必须单独拎出来,设置需要登录验证,否则授权无效 filterMap.put("/update", "authc");//授权的页面必须单独拎出来,设置需要登录验证,否则授权无效 filterMap.put("/add", "perms[add]");//[]中的字符串要跟数据库中perms字段存的字符串保持一致 filterMap.put("/update", "perms[update]");//[]中的字符串要跟数据库中perms字段存的字符串保持一致
2、在UserRealm类中的doGetAuthorizationInfo方法中赋予当前登录的用户什么样的权限
/** * 执行授权逻辑 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行授权逻辑"); //给当前登录用户 资源授权,授权字符串要跟ShiroConfig类中filterMap.put("/add", "perms[add]");的[]字符串一致 SimpleAuthorizationInfo sai = new SimpleAuthorizationInfo(); // 获取到当前登录用户 subject Subject subject = SecurityUtils.getSubject(); //这个principal就是下面登录认证逻辑中return new SimpleAuthenticationInfo(user,user.getPassword(),"");第一个参数user User user = (User) subject.getPrincipal(); // 根据用户名查询数据库 User dbUser = userService.findByName(user.getName()); // 根据数据库查询到的权限信息,赋予该用户权限 sai.addStringPermission(dbUser.getPerms()); return sai; }
ShiroConfig类中的权限拦截规则也可以设置为基于注解的权限拦截
1、在shiroConfig类中添加
/** * 开启shiro注解支持,例如@RequiresRoles()和@RequiresPermissions() * shiro需要结合Spring的aop实现 */ @Bean public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator dapc = new DefaultAdvisorAutoProxyCreator(); dapc.setProxyTargetClass(true); return dapc; } @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){ AuthorizationAttributeSourceAdvisor asa = new AuthorizationAttributeSourceAdvisor(); asa.setSecurityManager(defaultWebSecurityManager); return asa; }
2、在controller中,添加授权异常的拦截方法
/** 自定义异常拦截,没有授权的异常,跳转到请求 @RequestMapping("/unAuth") 当shiro出现权限验证失败后会抛出异常,因此必须写一个自定义的异常拦截,否则无法正常转发我们的没授权页面unAuth.html */ @ExceptionHandler(value = {UnauthorizedException.class}) public String unPerssion(Throwable throwable){ return "unAuth"; }
3、在请求的资源上添加注解
/** * @RequiresRoles(value = {"admin"}) * @RequiresPermissions(value = {"add","update"}) * 是shiro的注解,用来访问该方法或者类需要什么角色 */ @RequiresRoles(value = {"admin"}) @RequiresPermissions(value = {"add","update"}) @RequestMapping("/admin/hello") @ResponseBody public String hello(Model model){ return "Admin下面的哈喽!"; }