shiro初体验

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 }
原文地址:https://www.cnblogs.com/kikochz/p/12708592.html