shiro底层就是一个过滤器
springMvc采用请求转发,相当于只发送一次请求,转发的请求不会被拦截
redirect 重定向,相当于发送两次请求,重新发送的请求也会被拦截
springboot中默认放行的static下的文件也会被shiro拦截
redirect 重定向,相当于发送两次请求,重新发送的请求也会被拦截
springboot中默认放行的static下的文件也会被shiro拦截
1. 添加依赖
1 <dependency> 2 <groupId>com.alibaba</groupId> 3 <artifactId>druid-spring-boot-starter</artifactId> 4 <version>1.1.17</version> 5 </dependency> 6 <dependency> 7 <groupId>com.baomidou</groupId> 8 <artifactId>mybatis-plus-boot-starter</artifactId> 9 <version>3.3.1</version> 10 </dependency> 11 <dependency> 12 <groupId>org.apache.shiro</groupId> 13 <artifactId>shiro-spring</artifactId> 14 <version>1.5.2</version> 15 </dependency> 16 <dependency> 17 <groupId>com.github.theborakompanioni</groupId> 18 <artifactId>thymeleaf-extras-shiro</artifactId> 19 <version>2.0.0</version> 20 </dependency>
2. pojo类
1 //用户 2 @Data 3 @Accessors(chain = true) 4 public class User { 5 private Integer uId; 6 private String name; 7 private String password; 8 @TableField(exist = false) 9 private Set<Role> roles; 10 } 11 12 //角色 13 @Data 14 @Accessors(chain = true) 15 public class Role { 16 private Integer rId; 17 private String role; 18 private Set<Perm> perms; 19 } 20 21 //权限 22 @Data 23 @Accessors 24 public class Perm { 25 private Integer pId; 26 private String perm; 27 }
3.dao层
1 //@Mapper 2 //如果添加了@MapperScann就可以不用配置@Mapper,要添加在Application中不是test 3 public interface UserMapper extends BaseMapper<User> { 4 5 @Select("select * from t_user where name=#{username}") 6 @Results(id = "roleMap", value = { 7 //因为采用了Mybatis的注解配置查询,字段名与属性名不相同,要指出 8 @Result(property = "uId",column = "u_id",id = true), 9 @Result(property = "roles", column = "u_id", 10 many = @Many(select = "com.chz.dao.RoleMapper.queryRole", 11 fetchType = FetchType.LAZY)) 12 }) 13 User queryUserRoles(@Param("username") String username); 14 } 15 16 17 public interface RoleMapper extends BaseMapper<Role> { 18 //使用内嵌查询,User传过来uid通过第三张表拿到对应的r_id 19 @Select("select * from t_role t where r_id = (select r_id from t_u_r ur where #{u_id} = ur.u_id)") 20 @Results({ 21 @Result(property = "rId",column = "r_id",id = true), 22 @Result(property = "perms",column = "r_id", 23 many = @Many(select = "com.chz.dao.PermMapper.queryPerms", 24 fetchType = FetchType.LAZY)) 25 }) 26 List<Role> queryRole(@Param("u_id") Integer uId); 27 } 28 29 public interface PermMapper { 30 //然后通过传过来的r_id拿到对应的perm, 多个 perm用in 31 @Select("select * from t_perm where p_id in (select p_id from t_r_p where r_id = #{r_id})") 32 List<Perm> queryPerms(@Param("r_id") Integer rId); 33 }
4. service层
1 @Service 2 public class UserService extends ServiceImpl<UserMapper, User> implements IUserService { 3 @Autowired 4 UserMapper userMapper; 5 6 //同样可以加在Service的方法上来限定用户权限 7 // @RequiresRoles("admin") 8 public User queryOne(String usernmae) { 9 return userMapper.queryUserRoles(usernmae); 10 } 11 12 //可以通过shiro获取到session 13 public void testShiroSession(){ 14 Session session = SecurityUtils.getSubject().getSession(); 15 System.out.println(session.getAttribute("sessionKey")); 16 } 17 }
5. 编写自定义Realm
1 /** 2 * 继承AuthorzingRealm 3 * 用户认证后执行权限认证与授权 4 */ 5 public class CustomizeRealm1 extends AuthorizingRealm { 6 @Autowired 7 UserService userService; 8 9 /** 10 * 权限认证 11 * 12 * @param principals 即用户,底层是一个LinkedHashMap,先进先出 13 * @return 14 */ 15 @Override 16 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 17 if (principals == null) { 18 throw new UnknownAccountException(); 19 } 20 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); 21 //1. 从Principal中获取登入的用户信息,只有在需要权限认证时才会调用 22 String username = principals.getPrimaryPrincipal().toString(); 23 //2. 查询数据库或缓存中的用户 24 User users = userService.queryOne(username); 25 for (Role role : users.getRoles()) { 26 //获取角色,并给用户添加角色 27 simpleAuthorizationInfo.addRole(role.getRole()); 28 for (Perm perm : role.getPerms()) { 29 //获取角色权限,并给用户添加权限 30 simpleAuthorizationInfo.addStringPermission(perm.getPerm()); 31 } 32 } 33 //3.返回 34 return simpleAuthorizationInfo; 35 } 36 37 /** 38 * 用户认证 39 * MD5加密 40 * @param token token就是controller中设置的controller 41 * @return 42 * @throws AuthenticationException 43 */ 44 @Override 45 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 46 System.out.println("[FirstRealm] doGetAuthenticationInfo"); 47 //1. 将AuthenticationToken 转为UsernamePasswordToken 48 UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token; 49 //2. 从UsernamepasswordToken中获取username,表单输入的username 50 String username = usernamePasswordToken.getUsername(); 51 //3. 调用数据库方法,从数据库中查询出username对应的记录 52 User user = userService.getOne(new QueryWrapper<User>().eq("name", username)); 53 //4. 用户不存在,则抛出异常UnknownAccountException 54 // 有可能查询到的结果为null,需要判断一次 55 if (null == user) { 56 throw new UnknownAccountException(); 57 } 58 //5. 根据用户信息,决定是否抛出其他的AuthenticationException异常 59 if (Objects.equals(user.getName(), "老王")) { 60 throw new LockedAccountException(); 61 } 62 //6. 根据用户情况,来构建AuthenticationInfo对象,并返回 63 //以下信息是从数据库中获取的,除第三点外 64 //1). principal: 认证的实体信息,可以是usename,也可以是数据表对应的实体类对象 65 String principal = user.getName(); 66 //2). credentials: 密码 67 String credentials = user.getPassword(); 68 //3). 计算对应用户名的盐值,一般使用随机字符串或user id 69 ByteSource salt = ByteSource.Util.bytes(username); 70 //4). realmName: 当前realm对象的name,调用父类的getName()方法即可 71 String realmName = getName(); 72 //7. 密码校验由shiro校验,抛到controller中 73 //带上盐值比对数据库密码 74 SimpleAuthenticationInfo simpleAuthenticationInfo = 75 new SimpleAuthenticationInfo(principal, credentials, salt, realmName); 76 return simpleAuthenticationInfo; 77 } 78 }
6. 编写shiro配置类
1 @Configuration 2 public class ShiroConf { 3 /** 4 * Shiro 过滤url,交给SecurityManager代理 5 * 主要配置一些拦截的url,放行的url.. 6 * 7 * @param manager 安全代理 8 * @return 9 */ 10 @Bean 11 public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager manager) { 12 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 13 //必须使用LinkedHashMap否则会出现资源只能加载一次然后就被拦截的情况 14 LinkedHashMap<String, String> filterMap = new LinkedHashMap<>(); 15 /* 16 观察DefaultFilter获取过滤器 17 添加shiro内置过滤器,可以实现权限相关的拦截器,同样会拦截静态资源 18 常用过滤器: 19 1. anon: 无需认证(登入)可以访问资源 anonymous 20 2. authc: 必须认证才能访问 authority 21 3. user: 如果使用rememberMe的功能可以访问,同时认证通过的也可以访问 22 4. perms: 该资源必须得到资源权限才能访问 23 5. roles: 该资源必须得到角色权限才能访问 24 */ 25 //anon的请求必须放在authc之上 26 filterMap.put("/", "anon"); 27 filterMap.put("/index", "anon"); 28 filterMap.put("/login", "anon"); 29 filterMap.put("/logout", "anon"); 30 filterMap.put("/testSession", "anon"); 31 //一般通过数据库来设置用户权限 32 //[]中的是角色,要求认证通过同时要有用户角色 33 //filterMap.put("/add","auth,roles[user]"); 34 //[]中是权限 35 //filterMap.put("/delete","perms[user:delete]"); 36 filterMap.put("/add", "user"); 37 filterMap.put("/update", "user"); 38 filterMap.put("/**", "authc"); 39 //set的这些url都会被shiro自动放行 40 //设置未授权的提示页面,如果通过注解形式配置权限,setUnauthorizedUrl不会生效 41 shiroFilterFactoryBean.setUnauthorizedUrl("/unAuthc"); 42 //如果当前site没有cookie就会跳转到该页面 43 shiroFilterFactoryBean.setLoginUrl("/toLogin"); 44 //未生效 45 // shiroFilterFactoryBean.setSuccessUrl("/success"); 46 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); 47 //配置filter的代理 48 shiroFilterFactoryBean.setSecurityManager(manager); 49 return shiroFilterFactoryBean; 50 } 51 52 /** 53 * SecurityManager: 关联Realm,Subject,执行Realm用户认证,权限认证 54 * 55 * @param modularRealmAuthenticator 多realm认证器 56 * @param customizeRealm1 realm 1 57 * @param customizeRealm2 realm 2 58 * @return 59 */ 60 @Bean 61 public DefaultWebSecurityManager webSecurityManager( 62 ModularRealmAuthenticator modularRealmAuthenticator, 63 CustomizeRealm1 customizeRealm1, CustomizeRealm2 customizeRealm2, 64 CookieRememberMeManager cookieRememberMeManager) { 65 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 66 //配置多realm认证器 67 securityManager.setAuthenticator(modularRealmAuthenticator); 68 List<Realm> authorizingRealms = Arrays.asList(customizeRealm1, customizeRealm2); 69 securityManager.setRealms(authorizingRealms); 70 //配置Cookie管理器 71 securityManager.setRememberMeManager(cookieRememberMeManager); 72 return securityManager; 73 } 74 75 /** 76 * realm 1 采用MD5加密,用户只要满足其中一个realm即可认证 77 * 用于用户认证,和授权 78 * 79 * @param matcher 指定的密码匹配器 80 * @return 81 */ 82 @Bean 83 public CustomizeRealm1 customizeRealm1( 84 @Qualifier("MD5") HashedCredentialsMatcher matcher) { 85 CustomizeRealm1 customizeRealm1 = new CustomizeRealm1(); 86 //告诉Realm使用MD5加密 87 customizeRealm1.setCredentialsMatcher(matcher); 88 return customizeRealm1; 89 } 90 91 /** 92 * realm 2 采用SHA1加密,用户只要满足其中一个realm即可认证 93 * 用于用户认证,和授权 94 */ 95 @Bean 96 public CustomizeRealm2 customizeRealm2( 97 @Qualifier("SHA1") HashedCredentialsMatcher matcher) { 98 CustomizeRealm2 customizeRealm2 = new CustomizeRealm2(); 99 customizeRealm2.setCredentialsMatcher(matcher); 100 return customizeRealm2; 101 } 102 103 /** 104 * 开启多Realm认证,需要将该bean纳入SecurityManager管理 105 * 这里的Realm其实是SecurityManager传过来的 106 * 107 * @param customizeRealm1 108 * @param customizeRealm2 109 * @return 110 */ 111 @Bean 112 public ModularRealmAuthenticator authenticator( 113 CustomizeRealm1 customizeRealm1, CustomizeRealm2 customizeRealm2) { 114 ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator(); 115 /* 116 修改认证策略,默认AtLeastOneSuccessfulStrategy 117 AtLeastOneSuccessfulStrategy,只要一个realm验证成功即可 118 AllSuccessfulStrategy,要求所有realm验证成功 119 FirstSuccessfulStrategy,只要第一个realm验证成功即可,子realm忽略 120 */ 121 // authenticator.setAuthenticationStrategy(new AllSuccessfulStrategy()); 122 authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy()); 123 //关联多个realm 124 List<Realm> authorizingRealms = Arrays.asList(customizeRealm1, customizeRealm2); 125 authenticator.setRealms(authorizingRealms); 126 return authenticator; 127 } 128 129 /** 130 * 开启注解配置权限必须添加如下两个bean 131 * 132 * @param securityManager 安全代理 133 * @return 134 * @RequiresPermission 必须由指定权限 135 * @RequiresRole 必须具有指定角色 136 * @RequiresAuthentication 已经通过Subject login的 137 * @RequiresUser 已验证或者是已记住我的用户 138 * 配置shiro与spring关联 139 */ 140 @Bean 141 public AuthorizationAttributeSourceAdvisor advisor(DefaultWebSecurityManager securityManager) { 142 AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); 143 advisor.setSecurityManager(securityManager); 144 return advisor; 145 } 146 147 @Bean 148 public DefaultAdvisorAutoProxyCreator creator() { 149 DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); 150 creator.setProxyTargetClass(true); 151 return creator; 152 } 153 154 /** 155 * 开启shiro标签 156 * 157 * @return 158 */ 159 @Bean 160 public ShiroDialect shiroDialect() { 161 return new ShiroDialect(); 162 } 163 164 /** 165 * MD5加密 166 * 167 * @return 168 */ 169 @Bean 170 public HashedCredentialsMatcher MD5() { 171 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); 172 //指定加密方式为MD5 173 matcher.setHashAlgorithmName("MD5"); 174 //加密次数 175 matcher.setHashIterations(10); 176 matcher.setStoredCredentialsHexEncoded(true); 177 return matcher; 178 } 179 180 /** 181 * SHA1 加密 182 * 183 * @return 184 */ 185 @Bean 186 public HashedCredentialsMatcher SHA1() { 187 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); 188 //指定加密方式为SHA1 189 matcher.setHashAlgorithmName("SHA1"); 190 //加密次数 191 matcher.setHashIterations(10); 192 matcher.setStoredCredentialsHexEncoded(true); 193 return matcher; 194 } 195 196 /** 197 * cookie 198 * 199 * @return 200 */ 201 @Bean 202 public SimpleCookie rememeberMeookie() { 203 //对应前端的checkbox的name 204 SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); 205 //如果httyOnly设置为true,则客户端不会暴露给客户端脚本代码,使用HttpOnly cookie有助于减少某些类型的跨站点脚本攻击; 206 simpleCookie.setHttpOnly(true); 207 //设置cookie最长存活时间,单位秒 208 simpleCookie.setMaxAge(30); 209 return simpleCookie; 210 } 211 212 /** 213 * Cookie管理器 214 * 215 * @param cookie 216 * @return 217 */ 218 @Bean 219 public CookieRememberMeManager cookieRememberMeManager(SimpleCookie cookie) { 220 CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); 221 //rememberme cookie加密的密钥 222 byte[] cipherKey = Base64.decode("wrjUh2ttBPQLnT4JVhriug=="); 223 cookieRememberMeManager.setCipherKey(cipherKey); 224 cookieRememberMeManager.setCookie(cookie); 225 return cookieRememberMeManager; 226 } 227 228 }
7.编写controller层
1 @Controller 2 public class MyController { 3 @Autowired 4 private UserService userService; 5 6 /** 7 * 测试shiro service层调用session 8 */ 9 @RequestMapping("/testSession") 10 public String testShiroSession(HttpSession session) { 11 session.setAttribute("sessionKey", "sessionValue"); 12 userService.testShiroSession(); 13 return "/index"; 14 } 15 16 // @RequiresRoles("admin") 17 @GetMapping("/add") 18 public String add() { 19 return "list"; 20 } 21 22 // @RequiresRoles("admin")//只有指定的角色才有权限访问该uri 23 @GetMapping("/delete") 24 public String delete() { 25 return "list"; 26 } 27 28 @GetMapping("/update") 29 public String update() { 30 return "list"; 31 } 32 33 // @RequiresPermissions("add")//只有指定拥有权限才能访问该uri 34 @GetMapping("/select") 35 public String select() { 36 return "list"; 37 } 38 39 /** 40 * shiro跳转页面 41 */ 42 @GetMapping("/toLogin") 43 public String toLogin() { 44 return "login"; 45 } 46 47 /** 48 * 注销 49 */ 50 @GetMapping("/logout") 51 public String logout() { 52 Subject currentUser = SecurityUtils.getSubject(); 53 currentUser.logout(); 54 return "login"; 55 } 56 57 @PostMapping("/login") 58 public String login(@RequestParam(required = false) String username, 59 @RequestParam(required = false) String password, 60 @RequestParam(required = false) Boolean rememberMe, 61 Model model) { 62 //获取当前的用户 63 Subject currentUser = SecurityUtils.getSubject(); 64 //关联用户凭证 65 UsernamePasswordToken token = new UsernamePasswordToken(username, password); 66 try { 67 //如果页面不是一些重要的用户信息,可以采用rememberMe,如果是重要信息不能设置rememberMe 68 //记住我,会生成一个cookie,如果没有rememberMe默认是一个临时的cookie浏览器关闭就死亡 69 if (null != rememberMe) {//checkbox中如果钩中就是true,如果没有钩中就是null 70 //需要在login之前设置 71 token.setRememberMe(true); 72 } 73 /*调用login实际是securityManager的login(),然后调用authenticate() 74 如果是一个用户就会调用doSingleRealmAuthentication(),然后 75 调用realm的doGetAuthenticationInfo(token),进行用户认证 76 */ 77 currentUser.login(token); 78 return "index"; 79 } catch (UnknownAccountException e) { 80 model.addAttribute("msg", "用户名错误"); 81 return "login"; 82 } catch (IncorrectCredentialsException e) { 83 model.addAttribute("msg", "密码错误"); 84 return "login"; 85 } 86 } 87 }
8. 异常捕捉
1 @ControllerAdvice 2 public class ExceptionController { 3 //通过注解形式设置role和perms,setUnauthorizedUrl不生效,要配置全局异常捕捉 4 //捕捉权限认证失败 5 @ExceptionHandler(AuthorizationException.class) 6 @ResponseBody 7 public String authorizationException() { 8 return "你没有权限"; 9 } 10 11 //捕捉多realm验证失败,注意是shiro的包 12 @ExceptionHandler({AuthenticationException.class}) 13 @ResponseBody 14 public String authenticationException(){ 15 return "认证失败"; 16 } 17 }