Spring boot使用Shiro框架

1.maven引包

 1         <dependency>
 2             <groupId>org.apache.shiro</groupId>
 3             <artifactId>shiro-spring</artifactId>
 4             <version>1.2.5</version>
 5         </dependency>
 6         <dependency>
 7             <groupId>org.apache.shiro</groupId>
 8             <artifactId>shiro-ehcache</artifactId>
 9             <version>1.2.5</version>
10         </dependency>        

 

2.创建数据库

shiro一般需要5张表:3张实体表user,role,permission(一对一),2张关系映射表user-role,role-permission(一对多) 大致结构:

user:id name password ...

role : id name

permission:id name

user-role:uid rid

role-permission:rid pid

配置文件和model,mapper,service等构建过程不赘述,能实现查询数据库即可

 

3.创建抽象类AbstractUserRealm

package com.tqh.demo.config;

import com.tqh.demo.model.User;
import com.tqh.demo.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashSet;
import java.util.Set;

public abstract class AbstractUserRealm extends AuthorizingRealm {
    private static final Logger logger = LoggerFactory.getLogger(AbstractUserRealm.class);

    @Autowired
    private UserService userService;

    //获取用户组的权限信息
    public abstract UserRolesAndPermissions doGetGroupAuthorizationInfo(User userInfo);

    //获取用户角色的权限信息
    public abstract UserRolesAndPermissions doGetRoleAuthorizationInfo(User userInfo);

    /**
     * 获取授权信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String currentLoginName = (String) principals.getPrimaryPrincipal();
        Set<String> userRoles = new HashSet<>();
        Set<String> userPermissions = new HashSet<>();
        //从数据库中获取当前登录用户的详细信息
        User userInfo = userService.findUserByName(currentLoginName);
        if (null != userInfo) {
            UserRolesAndPermissions groupContainer = doGetGroupAuthorizationInfo(userInfo);
            UserRolesAndPermissions roleContainer = doGetGroupAuthorizationInfo(userInfo);
            userRoles.addAll(groupContainer.getUserRoles());
            userRoles.addAll(roleContainer.getUserRoles());
            userPermissions.addAll(groupContainer.getUserPermissions());
            userPermissions.addAll(roleContainer.getUserPermissions());
        } else {
            throw new AuthorizationException();
        }
        //为当前用户设置角色和权限
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addRoles(userRoles);
        authorizationInfo.addStringPermissions(userPermissions);
        logger.info("###【获取角色成功】[SessionId] => {}", SecurityUtils.getSubject().getSession().getId());
        return authorizationInfo;
    }

    /**
     * 登录认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //UsernamePasswordToken对象用来存放提交的登录信息
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //查出是否有此用户
        User user = userService.findUserByName(token.getUsername());
        if (user != null) {
            // 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验
            return new SimpleAuthenticationInfo(user.getNickname(), user.getPswd(), getName());
        }
        return null;
    }

    protected class UserRolesAndPermissions {
        Set<String> userRoles;
        Set<String> userPermissions;

        public UserRolesAndPermissions(Set<String> userRoles, Set<String> userPermissions) {
            this.userRoles = userRoles;
            this.userPermissions = userPermissions;
        }

        public Set<String> getUserRoles() {
            return userRoles;
        }

        public Set<String> getUserPermissions() {
            return userPermissions;
        }
    }
}

注:shiro通过Realm类实现用户的登录和权限验证

 

4.创建UserRealm实现上面的抽象类

 1 package com.tqh.demo.config;
 2 
 3 import com.tqh.demo.model.Permission;
 4 import com.tqh.demo.model.Role;
 5 import com.tqh.demo.model.User;
 6 import com.tqh.demo.service.PermissionService;
 7 import com.tqh.demo.service.RoleService;
 8 import org.springframework.beans.factory.annotation.Autowired;
 9 import org.springframework.stereotype.Component;
10 
11 import java.util.HashSet;
12 import java.util.Set;
13 
14 @Component
15 public class UserRealm  extends  AbstractUserRealm{
16 
17     @Autowired
18     RoleService roleService;
19     @Autowired
20     PermissionService permissionService;
21     @Override
22     public UserRolesAndPermissions doGetGroupAuthorizationInfo(User userInfo) {
23         Set<String> userRoles = new HashSet<>();
24         Set<String> userPermissions = new HashSet<>();
25         //获取当前用户下拥有的所有角色及权限
26         String[] roleIDs=roleService.findRolesByUser(userInfo.getId());
27         for(String id:roleIDs){
28             Role role=roleService.findRolebyID(id);
29             if(null!=role){
30                 userRoles.add(role.getName());
31                 //取出每个角色的所有权限
32                 String[] permisionIDs =permissionService.findPermissionsByRole(role.getId());
33                 for(String pid:permisionIDs){
34                     Permission permission=permissionService.findPermissionByID(pid);
35                     if(null!=permission){
36                         if(!userPermissions.contains(permission.getName())){
37                             userPermissions.add(permission.getName());
38                         }
39                     }
40                 }
41             }
42         }
43         return new UserRolesAndPermissions(userRoles, userPermissions);
44     }
45 
46     @Override
47     public UserRolesAndPermissions doGetRoleAuthorizationInfo(User userInfo) {
48         return doGetGroupAuthorizationInfo(userInfo);
49     }
50 }

 

 5.创建shiro的配置类ShiroConfiguration

  1 package com.tqh.demo.config;
  2 
  3 import org.apache.shiro.cache.ehcache.EhCacheManager;
  4 import org.apache.shiro.spring.LifecycleBeanPostProcessor;
  5 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
  6 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  7 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
  8 import org.slf4j.Logger;
  9 import org.slf4j.LoggerFactory;
 10 import org.apache.shiro.mgt.SecurityManager;
 11 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
 12 import org.springframework.context.annotation.Bean;
 13 import org.springframework.context.annotation.Configuration;
 14 import org.springframework.context.annotation.DependsOn;
 15 
 16 import java.util.LinkedHashMap;
 17 import java.util.Map;
 18 
 19 @Configuration
 20 public class ShiroConfiguration {
 21     private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);
 22 
 23     /**
 24      * Shiro的Web过滤器Factory 命名:shiroFilter<br /> * * @param securityManager * @return
 25      */
 26     @Bean(name = "shiroFilter")
 27     public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
 28         logger.info("注入Shiro的Web过滤器-->shiroFilter", ShiroFilterFactoryBean.class);
 29         ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
 30 
 31         //Shiro的核心安全接口,这个属性是必须的
 32         shiroFilterFactoryBean.setSecurityManager(securityManager);
 33         //要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面
 34         shiroFilterFactoryBean.setLoginUrl("/login");
 35         //登录成功后要跳转的连接,逻辑也可以自定义,例如返回上次请求的页面
 36         shiroFilterFactoryBean.setSuccessUrl("/index");
 37         //用户访问未对其授权的资源时,所显示的连接, 使用perms,roles,ssl,rest,port等filter才有效,最好使用全局异常处理
 38         shiroFilterFactoryBean.setUnauthorizedUrl("/403");
 39         /*定义shiro过滤器,例如实现自定义的FormAuthenticationFilter,需要继承FormAuthenticationFilter **本例中暂不自定义实现,在下一节实现验证码的例子中体现 */
 40 
 41         /*定义shiro过滤链 Map结构 * Map中key(xml中是指value值)的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 * anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 * authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter */
 42         Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
 43         // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
 44         filterChainDefinitionMap.put("/logout", "logout");
 45 
 46         // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
 47         // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
 48         filterChainDefinitionMap.put("/login", "anon");//anon 可以理解为不拦截
 49         filterChainDefinitionMap.put("/403", "anon");
 50         filterChainDefinitionMap.put("/reg", "anon");
 51         filterChainDefinitionMap.put("/plugins/**", "anon");
 52         filterChainDefinitionMap.put("/pages/**", "anon");
 53         filterChainDefinitionMap.put("/api/**", "anon");
 54         filterChainDefinitionMap.put("/dists/img/*", "anon");
 55         filterChainDefinitionMap.put("/**", "authc");//表示需要认证才可以访问
 56 
 57         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
 58 
 59         return shiroFilterFactoryBean;
 60     }
 61 
 62     @Bean
 63     public EhCacheManager ehCacheManager() {
 64         EhCacheManager cacheManager = new EhCacheManager();
 65         return cacheManager;
 66     }
 67 
 68     /**
 69      * 不指定名字的话,自动创建一个方法名第一个字母小写的bean * @Bean(name = "securityManager") * @return
 70      */
 71     @Bean
 72     public SecurityManager securityManager(UserRealm userRealm) {
 73         logger.info("注入Shiro的Web过滤器-->securityManager", ShiroFilterFactoryBean.class);
 74         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
 75         securityManager.setRealm(userRealm);
 76         //注入缓存管理器;
 77         securityManager.setCacheManager(ehCacheManager());//这个如果执行多次,也是同样的一个对象;
 78         return securityManager;
 79     }
 80 
 81     /**
 82      * Shiro生命周期处理器 * @return
 83      */
 84     @Bean
 85     public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
 86         return new LifecycleBeanPostProcessor();
 87     }
 88 
 89     /**
 90      * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能 * @return
 91      */
 92     @Bean
 93     @DependsOn({"lifecycleBeanPostProcessor"})
 94     public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
 95         DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
 96         advisorAutoProxyCreator.setProxyTargetClass(true);
 97         return advisorAutoProxyCreator;
 98     }
 99 
100     @Bean
101     public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
102         AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
103         authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
104         return authorizationAttributeSourceAdvisor;
105     }
106 }

注:需要shiro实现的功能都在这里配置,比如remenber me等等,此处未添加 

 

6.添加全局异常处理

 1 package com.tqh.demo.exception;
 2 
 3 import org.apache.shiro.authc.ExcessiveAttemptsException;
 4 import org.apache.shiro.authc.IncorrectCredentialsException;
 5 import org.apache.shiro.authc.LockedAccountException;
 6 import org.apache.shiro.authc.UnknownAccountException;
 7 import org.apache.shiro.authz.AuthorizationException;
 8 import org.slf4j.Logger;
 9 import org.slf4j.LoggerFactory;
10 import org.springframework.web.bind.annotation.ControllerAdvice;
11 import org.springframework.web.bind.annotation.ExceptionHandler;
12 
13 
14 /**
15  * @Author: Mcorleon
16  * @Date: 18-7-26 19:55
17  */
18 @ControllerAdvice
19 public class GlobalExceptionHandler {
20     private final static Logger logger= LoggerFactory.getLogger(GlobalExceptionHandler.class);
21 
22     @ExceptionHandler(AuthorizationException.class)
23     public String AuthenticationExceptionHandler(AuthorizationException e){
24         logger.error("权限错误");
25         logger.error(e.getMessage());
26         return  "403";
27     }
28 
29     @ExceptionHandler(UnknownAccountException.class)
30     public String AUnknownAccountExceptionHandler(UnknownAccountException e){
31         logger.error("未知用户");
32         logger.error(e.getMessage());
33         return  "Err";
34     }
35     @ExceptionHandler(IncorrectCredentialsException.class)
36     public String IncorrectCredentialsExceptionHandler(IncorrectCredentialsException e){
37         logger.error("校验出错");
38         logger.error(e.getMessage());
39         return  "Err";
40     }
41     @ExceptionHandler(LockedAccountException.class)
42     public String LockedAccountExceptionHandler(LockedAccountException e){
43         logger.error("账户已锁定");
44         logger.error(e.getMessage());
45         return  "Err";
46     }
47     @ExceptionHandler(ExcessiveAttemptsException.class)
48     public String ExcessiveAttemptsExceptionHandler(ExcessiveAttemptsException e){
49         logger.error("错误次数过多");
50         logger.error(e.getMessage());
51         return  "Err";
52     }
53 
54 }

 

注: 出现AuthorizationException异常,也就是无权限访问时返回403前缀以进入”/403“控制器。

前面配置类里规定的shiroFilterFactoryBean.setUnauthorizedUrl("/403") 对使用perms,roles,ssl,rest,port等filter的链接才有效。

 

7.测试

Controller:

 1 package com.tqh.demo.controller;
 2 
 3 import org.apache.shiro.SecurityUtils;
 4 import org.apache.shiro.authc.*;
 5 import org.apache.shiro.authz.annotation.RequiresRoles;
 6 import org.apache.shiro.subject.Subject;
 7 import org.apache.shiro.web.util.SavedRequest;
 8 import org.apache.shiro.web.util.WebUtils;
 9 import org.slf4j.Logger;
10 import org.slf4j.LoggerFactory;
11 import org.springframework.stereotype.Controller;
12 import org.springframework.web.bind.annotation.GetMapping;
13 import org.springframework.web.bind.annotation.PostMapping;
14 import org.springframework.web.bind.annotation.RequestMapping;
15 
16 import javax.servlet.http.HttpServletRequest;
17 
18 @Controller
19 @RequestMapping("/")
20 public class MainController {
21     private static final Logger logger = LoggerFactory.getLogger(MainController.class);
22     @GetMapping("/login")
23     public String loginPage(){
24         return "login";
25     }
26 
27     @PostMapping("/login")
28     public String login(String id , String psw, HttpServletRequest request) {
29 
30         String loginName =id;
31         String password=psw;
32         logger.info("准备登陆用户 => {}", loginName);
33         UsernamePasswordToken token = new UsernamePasswordToken(loginName,password);
34         //获取当前的Subject
35         Subject currentUser = SecurityUtils.getSubject();
36         //在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
37         //每个Realm都能在必要时对提交的AuthenticationTokens作出反应
38         //所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
39         logger.info("对用户[" + loginName + "]进行登录验证..验证开始");
40         currentUser.login(token);
41         logger.info("对用户[" + loginName + "]进行登录验证..验证通过");
42 
43         //验证是否登录成功
44         if (currentUser.isAuthenticated()) {
45             logger.info("用户[" + loginName + "]登录认证通过"+" sessionID:"+currentUser.getSession().getId().toString());
46             SavedRequest savedRequest = WebUtils.getSavedRequest(request);
47             // 获取保存的URL
48             if (savedRequest == null || savedRequest.getRequestUrl() == null) {
49                 return "redirect:/index";
50             }
51             //跳转到登陆之前的页面
52             return "redirect:" + savedRequest.getRequestUrl();
53 
54         } else {
55             token.clear();
56             return "redirect:/login";
57         }
58     }
59 
60     @RequestMapping("/index")
61     public String loginSuccess(){
62         return "index";
63     }
64 
65 
66     @RequestMapping("/403")
67     public String accessFault(){
68         return "403";
69     }
70 
71     @RequiresRoles("user")
72     @RequestMapping("/test")
73     public String accessTest(){
74         return "test";
75     }
76 }

新建一个简单的登录html即可测试,不赘述

subject的login()方法会触发Realm的doGetAuthenticationInfo()方法

添加了@RequiresRoles("user")标签就需要user角色才能访问,未登录会自动进入login页面,登陆成功配置了跳转入先前页面,触发Realm的doGetAuthorizationInfo方法检查权限,无权限则进入403页面

 

posted @ 2018-07-27 14:07  mcorleon  阅读(433)  评论(0编辑  收藏  举报