shiro安全框架

标签: java

今天学了一下shiro安全框架根据别人的文章实验成功 特地记录一下

原文转自:https://www.jianshu.com/p/7f724bec3dc3

我用的是springboot框架集成的

1 项目架构

在这里插入图片描述

2 导入依赖

<!--        shiro安全框架-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>

3 创建实体类

	3.1  用户类
//用户对应的实体类
@Data
@EqualsAndHashCode
@AllArgsConstructor
public class User implements Serializable {
    private String id;
    private String userName;
    private String password;
    /**
     * 用户对应的角色集合
     */
    private Set<Role> roles;
}

3.2 角色类

//角色对应的实体类
@Data
@EqualsAndHashCode
@AllArgsConstructor
public class Role implements Serializable {
    private String id;
    private String roleName;
    /**
     * 角色对应权限集合
     */
    private Set<Permissions> permissions;
}

3.3 权限类

//权限对应的实体类
@Data
@EqualsAndHashCode
@AllArgsConstructor
public class Permissions  implements Serializable {
    private String id;
    private String permissionsName;
}

4 开始编写逻辑层

4.1 接口

 User getUserByName(String getMapByName);

4.2 继承类
添加了两个角色:
wsl 123456 角色:admin 权限:add query
zhangsan 123456 角色:user 权限:query

 //模拟从数据库查询数据
    @Override
    public User getUserByName(String getMapByName) {
        //模拟数据库查询,正常情况此处是从数据库或者缓存查询。

        return getMapByName(getMapByName);
    }

    /**
     * 模拟数据库查询
     * @param userName
     * @return
     */
    private User getMapByName(String userName){
        //共添加两个用户,两个用户都是admin一个角色,
        //wsl有query和add权限,zhangsan只有一个query权限
        Permissions permissions1 = new Permissions("1","query");
        Permissions permissions2 = new Permissions("2","add");
        Set<Permissions> permissionsSet = new HashSet<>();
        permissionsSet.add(permissions1);
        permissionsSet.add(permissions2);
        Role role = new Role("1","admin",permissionsSet);
        Set<Role> roleSet = new HashSet<>();
        roleSet.add(role);
        User user = new User("1","wsl","123456",roleSet);
        Map<String ,User> map = new HashMap<>();
        map.put(user.getUserName(), user);

        Permissions permissions3 = new Permissions("3","query");
        Set<Permissions> permissionsSet1 = new HashSet<>();
        permissionsSet1.add(permissions3);
        Role role1 = new Role("2","user",permissionsSet1);
        Set<Role> roleSet1 = new HashSet<>();
        roleSet1.add(role1);
        User user1 = new User("2","zhangsan","123456",roleSet1);
        map.put(user1.getUserName(), user1);
        return map.get(userName);
    }

5 开始写shiro的配置文件 在config包里面写一个ShiroConfig 配置类

把CustomRealm和SecurityManager等加入到spring容器

//shiro安全框架的配置类
@Configuration
public class ShiroConfig {
    //不加这个注解不生效,具体不详
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }
    //将自己的验证方式加入容器
    @Bean
    public CustomRealm myShiroRealm() {
        CustomRealm customRealm = new CustomRealm();
        return customRealm;
    }
    //权限管理,配置主要是Realm的管理认证
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

    //Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> map = new HashMap<>();
        //登出
        map.put("/logout", "logout");
        //对所有用户认证
        map.put("/**", "authc");
        //登录
        shiroFilterFactoryBean.setLoginUrl("/login");
        //首页
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //错误页面,认证不通过跳转
        shiroFilterFactoryBean.setUnauthorizedUrl("/error");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    //加入注解的使用,不加入这个注解不生效
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }


    //shiro 的过滤器
    //@Bean(name = "shiroFilter")
    //public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
    //    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    //    shiroFilterFactoryBean.setSecurityManager( securityManager);
    //    //设置登陆页面
    //    shiroFilterFactoryBean.setLoginUrl("/login");
    //    //权限不足跳转页面
    //    shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
    //
    //    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    //    // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
    //    filterChainDefinitionMap.put("/webjars/**", "anon");
    //    filterChainDefinitionMap.put("/login", "anon");
    //    filterChainDefinitionMap.put("/", "anon");
    //    filterChainDefinitionMap.put("/front/**", "anon");
    //    filterChainDefinitionMap.put("/api/**", "anon");
    //
    //    filterChainDefinitionMap.put("/admin/**", "authc");
    //    filterChainDefinitionMap.put("/user/**", "authc");
    //    //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证
    //    filterChainDefinitionMap.put("/**", "authc");
    //    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    //    return shiroFilterFactoryBean;
    //
    //}

6 写CustomRealm类 在shiro包下 自定义Realm用于查询用户的角色和权限信息并保存到权限管理器

public class CustomRealm extends AuthorizingRealm {
    @Resource
    private DemoService demoService;

    // 权限认证,即登录过后,每个身份不一定,对应的所能看的页面也不一样。
    //使用shiro时,如果正常登陆(执行subject.login(token)成功)就能在全局通过SecurityUtils.getSubject().getPrincipal()获取用户信息
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取用户登陆名
        String username = (String) SecurityUtils.getSubject().getPrincipal();
        //根据用户登录名  查询用户信息
        User user = demoService.getUserByName(username);

        //添加角色权限
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //先遍历用户的角色
        for (Role role : user.getRoles()) {
            //添加角色
            info.addRole(role.getRoleName());
            //添加权限
            for (Permissions permission : role.getPermissions()) {
                info.addStringPermission(permission.getPermissionsName());
            }
        }

        //Set<String> stringSet = new HashSet<>();
        //stringSet.add("user:show");
        //stringSet.add("user:admin");
        //info.setStringPermissions(stringSet);
        return info;
    }

    /**
     * 这里可以注入userService,为了方便演示,我就写死了帐号了密码
     * private UserService userService;
     * <p>
     * 获取即将需要认证的信息
     */
    //身份认证。即登录通过账号和密码验证登陆人的身份信息。
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("-------身份认证方法--------");
        //加这一步的目的是在Post请求的时候会先进认证,然后在到请求
        //先得到用户的身份 看看是否为空
        if (authenticationToken.getPrincipal() == null){
            return  null;
        }
        //获取用户的名字
        String name = authenticationToken.getPrincipal().toString();
        //根据用户的名字 调用逻辑层的接口 获取用户的信息
        User userByName = demoService.getUserByName(name);
        if (userByName == null){
            return null;
        }
        else {
            ////这里验证authenticationToken和simpleAuthenticationInfo的信息
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userByName.getUserName(), userByName.getPassword(), getName());
            return simpleAuthenticationInfo;
        }

        //
        //String userName = (String) authenticationToken.getPrincipal();
        //String userPwd = new String((char[]) authenticationToken.getCredentials());
        ////根据用户名从数据库获取密码
        //String password = "123";
        //if (userName == null) {
        //    throw new AccountException("用户名不正确");
        //} else if (!userPwd.equals(password )) {
        //    throw new AccountException("密码不正确");
        //}
        //
        ////交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配
        //return new SimpleAuthenticationInfo(userName, password,
        //        ByteSource.Util.bytes(userName + "salt"), getName());
    }

}

7 写控制器

@RestController
public class DemoController {
    @Resource
    private DemoService demoService;

    @RequestMapping("/login")
    public String login(User user){
        //添加用户的认证信息
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                user.getUserName(),
                user.getPassword()
        );
        try {
            //进行验证,这里可以捕获异常,然后返回对应信息
            subject.login(usernamePasswordToken);
//            subject.checkRole("admin");
//            subject.checkPermissions("query", "add");
        } catch (UnknownAccountException uae) {
            return "未知账户";
        } catch (IncorrectCredentialsException ice) {
            return "密码不正确";
        } catch (LockedAccountException lae) {
            return "账户已锁定";
        } catch (ExcessiveAttemptsException eae) {
            return "用户名或密码错误次数过多";
        } catch (AuthenticationException ae) {
            return "用户名或密码不正确!";
        } catch (AuthorizationException e) {
            e.printStackTrace();
            return "没有权限";
        }
        return "login success";
    }
    //注解验角色和权限
    @RequiresRoles("user")
    @RequiresPermissions("query")
    @RequestMapping("/index")
    public String index() {
        return "index!";
    }
    
    }

8 注解验证角色和权限的话无法捕捉异常,从而无法正确的返回给前端错误信息,所以我加了一个类用于拦截异常,具体代码如下 MyExceptionHandler.java

package com.wsl.filter;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
@Slf4j
public class MyExceptionHandler {

    @ExceptionHandler
    @ResponseBody
    public String ErrorHandler(AuthorizationException e) {
        log.error("没有通过权限验证!", e);
        return "没有通过权限验证!";
    }
}

9 开始测试 我用的是postman

使用用户 wsl登陆的时候
在这里插入图片描述
当账户/密码错误的时候
在这里插入图片描述
在这里插入图片描述
使用wsl用户 登陆 index的时候
在这里插入图片描述
使用zhangsan用户登陆index的时候
先用zhangsan 帐号完成登陆
在这里插入图片描述
在这里插入图片描述

版权声明:本文为duanduan339原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/duanduan339/article/details/105072320