小言_互联网的博客

基于springboot下利用shiro注解完成权限控制的超详细记录

372人阅读  评论(0)

首先先说明一下,由于某些问题,不能将代码上传到github,但是可以展示出足够说明流程的代码和sql。

项目涉及到的技术:redis,shiro,springboot,jpa

权限分配架构:RABC模型

数据库截图



务必注意这个id=11123的exm,是用它举的例子,总结一下上面的截图:

这个id=11123的人,拥有名字叫boss的角色名,而这个叫boss的角色,拥有名为boss的权限名。

这里只展示一个功能为添加User接口的权限

一、model层

User

@Entity
@Table(name = "user")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {

    @Id
    private String id;

    private String name;

    private String city;

    private String age;

    private String sex;

    private String dept;

    private String company;

    private String level;

    @JsonIgnore
    @ManyToMany
    @JoinTable(name = "user_role",
            joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "id")},
    inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")})
    private Set<Role> roles = new HashSet<>();
}

Role

@Entity
@Table(name = "role")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {

    @Id
    private String id;

    private String name;

    private String description;

    @JsonIgnore
    @ManyToMany(mappedBy = "roles")
    private Set<User> users = new HashSet<>();

    @JsonIgnore
    @ManyToMany
    @JoinTable(name = "role_permission",
    joinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")},
    inverseJoinColumns = {@JoinColumn(name = "permission_id",referencedColumnName = "id")})
    private Set<Permission> permissions = new HashSet<>();


}

Permission

@Entity
@Table(name = "permission")
@AllArgsConstructor
@NoArgsConstructor
@Data
@DynamicInsert(true)
@DynamicUpdate(true)
public class Permission {

    @Id
    private String id;

    /**
     * 权限名
     */
    private String name;

    private String type;

    private String code;

    private String description;

    public Permission(String name, String type, String code, String description) {
        this.name = name;
        this.type = type;
        this.code = code;
        this.description = description;
    }

}

至于@ManytoMany这种注解我就不多解释了,可以自己去百度。

二、dao

UserDao

public interface UserDao extends JpaSpecificationExecutor<User>, JpaRepository<User,String> {
}

RoleDao和PermissionDao同理,就不多解释了,就是继承jpa那两个而已。

三、Service

注意:这里我没有写interface,直接用的实体类。

	/**
     * 添加员工
     */
    public void add(Map map) {
        User user = new User();
        user.setAge(String.valueOf(map.get("age")));
        user.setCity(String.valueOf(map.get("city")));
        user.setCompany("TGU");
        user.setId(String.valueOf(idWorker.nextId()));
        user.setLevel("3");
        user.setDept(String.valueOf(map.get("dept")));
        user.setSex(String.valueOf(map.get("sex")));
        user.setName(String.valueOf(map.get("name")));
        userDao.save(user);
    }

四、Controller

	@PutMapping("/add")
    public Result add(@RequestBody Map map) {
        userService.add(map);
        return new Result(ResultCode.SUCCESS);
    }

五、Realm

构建安全数据源之前,我们需要在用户认证方面,对于实际要分配的权限,进行return,换句话说,我在这里的思路是:

你登录的时候就给你一个sessionId,在这个sessionId里面携带者权限,等级相关信息,一起进行登录,这样前端拿到数据了,也可以从前端角度去控制(当然这样是很不安全的,还是从后端去控制吧)

首先构建出来你真正希望登录的时候携带的数据

ProfileResult

@Getter
@Setter
public class ProfileResult implements AuthCachePrincipal, Serializable {


    private String name;
    private String city;
    private String level;
    private Map<String, Object> roles = new HashMap<>();


    public ProfileResult(User user, List<Permission> list) {
        this.city = user.getCity();
        this.level = user.getLevel();
        this.name = user.getName();
        Set<String> permissions = new HashSet<>();

        for (Permission perm : list) {
            String name = perm.getName();
            permissions.add(name);
        }
        this.roles.put("permissions", permissions);
    }

    public ProfileResult(User user) {
        this.city = user.getCity();
        this.level = user.getLevel();
        this.name = user.getName();
        Set<Role> roles = user.getRoles();
        Set<String> permissions = new HashSet<>();
        for (Role role : roles) {
            Set<Permission> perms = role.getPermissions();
            for (Permission permission : perms) {
                String name = permission.getName();
                permissions.add(name);
            }
        }
        this.roles.put("permissions", permissions);
    }

    @Override
    public String getAuthCacheKey() {
        return null;
    }
}

在Realm里,我们需要完成的就是认证和授权。

在认证里,我们需要做的是验证账号密码,然后再账号密码正确的情况下,把这个人的ProfileResult给他。

在授权里,我们要将这个人的角色和权限注入到shiro内部,让后面的注解可以直接根据数据库的内容去生效。

UserRealm

public class UserRealm extends AuthorizingRealm {

    @Autowired
    private PasswordService passwordService;

    @Autowired
    private UserService userService;

    @Autowired
    private PermissionService permissionService;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        //获取到安全数据
        ProfileResult result = (ProfileResult) principalCollection.getPrimaryPrincipal();
        //获取角色信息
        Set<String> values = result.getRoles().keySet();
        //获取权限信息
        Set<String> strings = (Set<String>) result.getRoles().get("permissions");
        //构造权限数据
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRoles(values);
        info.addStringPermissions(strings);


//        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//        User user = (User) principalCollection.getPrimaryPrincipal();
//        for (Role role : user.getRoles()) {
//            info.addRoles(Collections.singleton(role.getName()));
//            for (Permission permission : role.getPermissions()) {
//                info.addStringPermissions(Collections.singleton(permission.getName()));
//            }
//        }
        return info;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取到用户的名字和密码
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        String username = usernamePasswordToken.getUsername();
        String password = new String(usernamePasswordToken.getPassword());
        System.out.println(password);
        //根据名字查询用户
        User user = userService.findByName(username);
        //根据用户对象获取到密码
        String byId = passwordService.findById(user.getId());
        //判断用户是否存在
        if (user != null && password.equals(byId)) {
            //构造安全数据并且返回
            ProfileResult result = null;
            Map map = new HashMap();
            List<Permission> permissionsByName = permissionService.findAll(map);
            result = new ProfileResult(user,permissionsByName);
            return new SimpleAuthenticationInfo(result, byId, this.getName());//这里的东西将会被存储到SessionId里
        }
        //用户名和密码不匹配
        return null;
    }
}

六、ShiroConfiguration

上面对于数据源之类的还有认证授权都完事了,然后就将这些东西注入到Shiro的配置里面,并且标记上@Configuration注入到spring容器就生效了。

因为项目在login的时候,使用到了redis,所以也一并将redis放里面了,后面也会写出针对于redis的记录。

ShiroConfiguration

@Configuration
public class ShiroConfiguration {

    //1.创建Realm
    @Bean
    public UserRealm getRealm() {
        return new UserRealm();
    }
    //2. 创建安全管理器
    @Bean
    public SecurityManager getSecurityManager(UserRealm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);

        //将自定义的会话管理器注册到安全管理器中
        securityManager.setSessionManager(sessionManager());
        //将自定义的redis缓存管理器注册到安全管理器中
        securityManager.setCacheManager(cacheManager());

        return securityManager;
    }

    //开启对shior注解的支持
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    //3.配置shiro的过滤器工厂
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        //1.创建过滤器工厂
        ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
        //2.设置安全管理器
        filterFactory.setSecurityManager(securityManager);
        //3.通用配置(跳转登录页面,未授权跳转的页面)
        filterFactory.setLoginUrl("/autherror?code=1");//跳转url地址
        filterFactory.setUnauthorizedUrl("/autherror?code=2");//未授权的url
        //4.设置过滤器集合
        Map<String,String> filterMap = new LinkedHashMap<>();
        //anon -- 匿名访问
        filterMap.put("/user/login","anon");
        filterMap.put("/autherror","anon");
        filterMap.put("/user/register", "anon");
        //注册
        //authc -- 认证之后访问(登录)
        filterMap.put("/**","authc");
        //perms -- 具有某中权限 (使用注解配置授权)
        filterFactory.setFilterChainDefinitionMap(filterMap);

        return filterFactory;
    }

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;

    /**
     * 1.redis的控制器,操作redis
     */
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        return redisManager;
    }

    /**
     * 2.sessionDao
     */
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO sessionDAO = new RedisSessionDAO();
        sessionDAO.setRedisManager(redisManager());
        return sessionDAO;
    }

    /**
     * 3.会话管理器
     */
    public DefaultWebSessionManager sessionManager() {
        CustomSessionManager sessionManager = new CustomSessionManager();
        sessionManager.setSessionIdCookieEnabled(false);
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }

    /**
     * 4.缓存管理器
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

}

七、login

既然提到了redis,就顺便简单说一下login的过程,这个与此文章无关,如果单纯针对shiro,可以跳过了。

我们使用Subject,提取出安全数据源的东西,并且将sessionId,从redis中提取出来,返回给前端,在登录的时候,利用sessionId进行权限认证和授权。

    /**
     * 登录
     */
    @PostMapping("/login")
    public Result login(@RequestBody Map<String,Object> map) {

        String name = String.valueOf(map.get("name"));
        String password = String.valueOf(map.get("password"));
        try {
            //1.构造登录令牌
            //加密密码
//            password = new Md5Hash(password,name,3).toString();
            UsernamePasswordToken token = new UsernamePasswordToken(name, password);
            //获取subject
            Subject subject = SecurityUtils.getSubject();
            //调用login方法完成认证
            subject.login(token);
            //获取SessionId
            String sessionId = (String) subject.getSession().getId();
            return new Result(ResultCode.SUCCESS,sessionId);
        } catch (Exception e) {
            e.printStackTrace();
            return new Result(ResultCode.MOBILEORPASSWORDERROR);
        }
    }

八、权限分配

最开始我们就说了,id为11123的这个人有名字叫做boss的权限,所以我们就给他标记为boss

    /**
     * 添加员工
     */
//    @RequiresPermissions("boss")
    @PutMapping("/add")
    public Result add(@RequestBody Map map) {
        userService.add(map);
        return new Result(ResultCode.SUCCESS);
    }

这里我们先打上注释,然后启动程序,去试试看

利用session进行正常登录,认证和授权如下

然后操作成功。

然后我们把注释去掉,改成一个压根就不存在的Permission去认证:

/**
     * 添加员工
     */
    @RequiresPermissions("asdasdasdasdasdasd")
    @PutMapping("/add")
    public Result add(@RequestBody Map map) {
        userService.add(map);
        return new Result(ResultCode.SUCCESS);
    }

重新启动程序

重新认证

访问接口

明显可以看出,我们的权限生效了。


转载:https://blog.csdn.net/qq_41936805/article/details/101827346
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场