SpringBoot整合Shiro简单实现

133 阅读2分钟

文章简述

本文通过使用SpringBoot整合Shiro实现认证、授权功能,仅提供简单实现操作。

关键代码实现

Shiro依赖引入

<!-- shiro -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.6.0</version>
</dependency>

配置文件参数配置

非必须,可自行决定其他配置方式

# shiro
shiro:
  user:
    loginUrl: /user/login
    unauthUrl: /user/unauth
  # minutes
  expireTime: 30

自定义Realm

/**
 * @ClassName: UserRealm
 * @Description: 自定义Realm
 * @Date: 2021/7/13 17:50
 */
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private MenuService menuService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权逻辑");
        // 获取当前登录用户
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        // 角色列表
        Set<String> roles = new HashSet<String>();
        // 功能列表
        Set<String> menus = new HashSet<String>();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        if("admin".equals(user.getLoginName())){
            // 管理员分配全部权限
            info.addRole("admin");
            info.addStringPermission("*:*:*");
        }else {
            roles = roleService.selectRoleKeys(user.getUserId());
            menus = menuService.selectPermsByUserId(user.getUserId());
            // 角色加入AuthorizationInfo认证对象
            info.setRoles(roles);
            // 权限加入AuthorizationInfo认证对象
            info.setStringPermissions(menus);
        }
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证逻辑");

        UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
        String username = upToken.getUsername();
        String password = "";
        if (upToken.getPassword() != null) {
            password = new String(upToken.getPassword());
        }

        User user = userService.checkLogin(username, password);
        if (null == user) {
            throw new UnknownAccountException("登录账号或密码错误,请重新输入!");
        }

        return new SimpleAuthenticationInfo(user, password, getName());
    }
}

定义Shiro配置类

/**
 * @ClassName: ShiroConfig
 * @Description: Shiro配置类
 * @Date: 2021/7/6 17:14
 */
@Configuration
public class ShiroConfig {

    /**
     * 登录地址
     */
    @Value("${shiro.user.loginUrl}")
    private String loginUrl;

    /**
     * 创建Realm
     */
    @Bean(name = "userRealm")
    public UserRealm getRealm() {
        return new UserRealm();
    }

    /**
     * 创建DefaultWebSecurityManager
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     * 创建ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 登录地址(仅仅设置登录路径,但是登录路径并没有设置anon)
        shiroFilterFactoryBean.setLoginUrl(loginUrl);
        /**
         * 添加Shiro内置过滤器
         * anon:无需认证(登录)即可访问
         * authc:必须认证才能访问
         * user:如果使用rememberMe的功能可以直接访问
         * perms:该资源必须得到资源权限才可以访问
         * role:该资源必须得到角色权限才可以访问
         */
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        /**
         * swagger
         */
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/configuration/ui", "anon");
        filterChainDefinitionMap.put("/swagger-resources/**", "anon");
        filterChainDefinitionMap.put("/configuration/security", "anon");
        filterChainDefinitionMap.put("/v2/api-docs", "anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");

        /**
         * 退出 logout地址,shiro去清除session
         */
//        filterChainDefinitionMap.put("/logout", "logout");

        /**
         * 无需认证
         */
        // 设置登录路径anon,否则访问登录接口,会多执行一次认证方法(token为空)
        filterChainDefinitionMap.put("/user/login", "anon");
        filterChainDefinitionMap.put("/user/testShiro", "anon");


        /**
         * 其他均需要认证
         */
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * Shiro生命周期处理器
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

    /**
     *   * 开启shiro aop注解支持.
     *   * 使用代理方式;所以需要开启代码支持;
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

}

定义异常类统一处理认证/授权失败跳转URL

/**
 * @ClassName: ExceptionHandle
 * @Description: 统一异常处理类
 * @Date: 2021/7/14 10:54
 */
@RestControllerAdvice
public class ExceptionHandle {

    /**
     * 登录失败跳转地址
     */
    @Value("${shiro.user.loginUrl}")
    private String loginUrl;

    /**
     * 权限认证失败跳转地址
     */
    @Value("${shiro.user.unauthUrl}")
    private String unauthUrl;

//    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandle.class);

    /**
     * 授权失败默认跳转的请求地址
     *
     * @param ex
     * @return
     */
    //    @ResponseBody
    @ExceptionHandler(UnauthorizedException.class)
    public Object handleShiroException(Exception ex) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName(unauthUrl);
        return modelAndView;
    }

    /**
     * 认证失败默认跳转的请求地址
     *
     * @param ex
     * @return
     */
    //    @ResponseBody
    @ExceptionHandler(AuthorizationException.class)
    public Object AuthorizationException(Exception ex) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName(loginUrl);
        return modelAndView;
    }
}

使用

/**
 * @ClassName: UserController
 * @Description: TODO
 * @Date: 2021/7/8 17:45
 */
@Api(tags = "用户管理模块")
@RestController
@RequestMapping("/user")
public class UserController {

    @Value("${shiro.expireTime}")
    private int expireTime;

    @Autowired
    private UserService userService;

    @ApiOperation(value = "用户登录", notes =
            "请求信息: \n" +
                    "&emsp; loginName &nbsp; : &nbsp; 登录账号  \n" +
                    "&emsp; password &nbsp; : &nbsp; 登录密码 \n" +
                    "返回码: \n" +
                    "&emsp; 200 &nbsp; : &nbsp; 成功  \n" +
                    "&emsp; 500 &nbsp; : &nbsp; 服务器错误  ")
    @PostMapping("/login")
    public HashMap<String, String> login(@RequestBody User user) {
        //用户主体信息
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                user.getLoginName(),
                user.getPassword()
        );
        HashMap<String, String> hashMap = new HashMap<>();
        try {
            //进行验证,这里可以捕获异常,然后返回对应信息
            subject.login(usernamePasswordToken);
        } catch (UnknownAccountException e) {
            hashMap.put("msg", "登录账号或密码错误,请重新输入!");
            return hashMap;
        } catch (AuthenticationException e) {
            hashMap.put("msg", "授权失败!");
            return hashMap;
        }
        // 登录成功后设置过期时间 单位毫秒
        subject.getSession().setTimeout(expireTime * 60 * 1000);
        return hashMap;
    }

    /**
     * 访问需求认证的资源路径,默认跳转登录页面,GET请求方式
     *
     * @return
     */
    @GetMapping("/login")
    public HashMap<String, String> loginGet() {
        HashMap<String, String> hashMap = new HashMap<String, String>();
        hashMap.put("msg","请先进行登录!");
        return hashMap;
    }

    /**
     * 退出登录
     *
     * @return
     */
    @ApiOperation(value = "退出登录")
    @GetMapping("/logout")
    public HashMap<String, String> logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        HashMap<String, String> hashMap = new HashMap<String, String>();
        hashMap.put("msg", "退出登录成功!");
        return hashMap;
    }

    /**
     * 没有授权跳转请求
     *
     * @return
     */
    @ApiOperation(value = "没有资源授权")
    @GetMapping("/unauth")
    public HashMap<String, String> unauth() {
        HashMap<String, String> hashMap = new HashMap<String, String>();
        hashMap.put("msg","你没有该资源的访问权限!");
        return hashMap;
    }

    @GetMapping("/testAuth")
    @RequiresPermissions("test:auth:test")
    public String testAuth(){
        return "[测试授权]权限";
    }

    @GetMapping("/testQueryAuth")
    @RequiresPermissions("system:button:query")
    public String testAuth1(){
        return "[查询按钮]权限";
    }
}

详细代码地址

link.zhihu.com/?target=htt…