shiro实现认证与授权

shiro实现认证与授权

贴一段官网文字

Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.

Apache Shiro™是一个功能强大且易于使用的Java安全框架,可用于身份验证、授权、加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序,从最小的移动应用程序到最大的web和企业应用程序。

注:此文档例子基于springboot version2.7.17

1.引入Maven依赖

		
		<dependency>
			<groupId>org.apache.shirogroupId>
			<artifactId>shiro-springartifactId>
			<version>1.13.0version>
		dependency>

2.设置realm

Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。

从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。

Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

/**
 * 定义自己的realm,实现doGetAuthorizationInfo和doGetAuthenticationInfo方法
 */
public class MyRealm extends AuthorizingRealm {
    /**
     * 授权,从数据源获取用户的角色和权限列表
     * @param principalCollection Subject的principal集合,主要属性是primaryPrincipal
     * @return simpleAuthorizationInfo Simple POJO implementation of the AuthorizationInfo interface
     *          that stores roles and permissions as internal attributes.
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 获取principal,subject的唯一标识,一般是userId,主要看登录时设置的是什么
        String primaryPrincipal = (String)principalCollection.getPrimaryPrincipal();
        // 通过上面的principal获取角色和权限列表
        // 模拟数据源获取角色
        List roleList = Arrays.asList("user_admin","blog_admin","system_admin");
        List permissionList = Arrays.asList("user:*","blog:*","system:*");
        // SimpleAuthorizationInfo是AuthorizationInfo接口的简单POJO实现,该接口将角色和权限存储为内部属性。
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(new HashSet<>(roleList));
        simpleAuthorizationInfo.setStringPermissions(new HashSet<>(permissionList));
        return simpleAuthorizationInfo;
    }

    /**
     * 登录认证,对登录携带的“token”进行登录认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String principal = (String)authenticationToken.getPrincipal();
        // 从数据源中查找是否存在principal的对象,可能是mysql,redis等等
        // 模拟判断数据源是否存在该数据,不存在返回空,标识认证不通过
        String account = "zhangsan";
        String password = "123456";
        if (!principal.equals(account)){
            return null;
        }
        // 存在返回SimpleAuthenticationInfo
        // 另外,SimpleAuthenticationInfo有
        SimpleAuthenticationInfo simpleAuthenticationInfo =
                new SimpleAuthenticationInfo(principal,password,this.getName());
        return simpleAuthenticationInfo;
    }
}

3.配置ShiroConfig

引入shiro,写好realm,我们需要使其生效,因而需要编写config类进行基础配置

/**
 * shiro配置类
 */
@Configuration
public class ShiroConfig {


    /**
     * 配置一个自定义的Realm的bean,最终将使用这个bean返回的对象来完成我们的认证和授权
     */
    @Bean
    public MyRealm myRealm() {
        MyRealm myRealm = new MyRealm();
        return myRealm;
    }

    /**
     *
     * @param myRealm
     * @return
     */
    @Bean
    public SecurityManager securityManager(Realm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置一个Realm,这个Realm是最终用于完成我们的认证号和授权操作的具体对象
        securityManager.setRealm(myRealm);
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        // 认证失败(用户没登录时)会重定向到这个loginUrl
        shiroFilter.setLoginUrl("/");
        // 授权失败重定向路径
        shiroFilter.setUnauthorizedUrl("/noPermission");
        // 定义一个LinkHashMap,保存认证的路径和规则
        Map<String,String> map = new LinkedHashMap();
        // key为uri,anon为标识符,标识不需要验证
        map.put("/login","anon");
        // authc表示Authentication,即是需要认证
        map.put("/admin/**","authc");
        // key “/**"表示所有uri,”/**“必须写在最后,如果不写该项,默认放行所有没配置的uri
        map.put("/**","authc");
        // 将该map设置进shiroFilter
        shiroFilter.setFilterChainDefinitionMap(map);
        return shiroFilter;
    }
}

4.测试接口类

@RestController
public class TestController {

    /**
     * 登录接口
     * @param username
     * @param password
     * @return
     */
    @GetMapping("/login")
    public String login(String username,String password){
            // 获取当前主体
            Subject subject = SecurityUtils.getSubject();
            // 构建登录token,login方法进入中进入我们自定义的realm的doGetAuthenticationInfo方法
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
            subject.login(usernamePasswordToken);
            return "登陆成功";
    }

    /**
     * 验证登录才能访问的接口
     * @return
     */
    @GetMapping("/testIndex")
    public String testIndex(){
        Subject subject = SecurityUtils.getSubject();
        System.out.println(subject.getSession().getId());
        return "成功进入主页";
    }

    /**
     * 重定向需要登录的接口
     * @return
     */
    @GetMapping("/")
    public String needLoginPage(){
        return "需要登录";
    }

}

测试结果如期望所得

  • 登录前访问 /testIndex 接口,跳转 / 接口提示 “需要登录”
  • 访问http://127.0.0.1:8080/login?username=zhangsan&password=123456进行登录,显示登录成功
  • 接着访问 /testIndex 接口,显示 “成功进入主页”

5. 权限校验

以上的测试展示了shiro的认证功能,但是授权并没涉及。下面讲一下shiro的授权。

首先,shiro是可以通过代码去进行校验是否认证通过或者有某个角色或权限的。

    public String testIndex(){
        // 检查是否已经登录(通过认证)
        boolean authenticated = SecurityUtils.getSubject().isAuthenticated();
        // 检查是否属于某个角色
        boolean user_admin = SecurityUtils.getSubject().hasRole("user_admin");
        // 检查是否拥有某个权限
        boolean permitted = SecurityUtils.getSubject().isPermitted("user:add");
        if (authenticated && user_admin && permitted){
            return "校验成功";
        }else{
            return "校验失败";
        }
    }

在执行SecurityUtils.getSubject().hasRole("user_admin")SecurityUtils.getSubject().isPermitted("user.add") 的时候,会进入realm的doGetAuthorizationInfo方法中,获取AuthorizationInfo对象(里面含有roles和permissions集合),然后进行对比。返回boolean值。

然后,为了减少shiro跟业务代码的耦合,shiro也提供了注解的方式进行拦截判断,但需要先在shiro的配置中开启shiro 的aop支持。

在上面介绍到的ShiroConfig配置类中添加方法

    /**
     * 开启shiro aop支持,(通过注解实现登录认证和权限校验)
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

然后就可以在方法前面添加@RequiresAuthentication@RequiresRoles@RequiresPermissions 进行配置实现。

    @GetMapping("/testIndex")
    @RequiresAuthentication     // 需要登录
    @RequiresRoles("user_admin")    // 需要user_admin角色
    @RequiresPermissions("user:add")    // 需要user.add权限
    public String testIndex(){
        return "成功进入首页";
    }

好,至此,shiro的认证与授权的简单实现就完成了。

此外,shiro在认证的doGetAuthenticationInfo方法的返回AuthenticationInfo对象中还能对密码进行加密,加密算法需要在ShiroConfig中设置,但是我没用过,我喜欢在自己的service中实现加密功能,因而不在本文中介绍,下次需要用的时候再补充。

你可能感兴趣的:(springboot)