基于Shiro框架的登录功能

Shiro框架是什么?

Apache Shiro是一个Java安全框架,它提供了一套易于使用的API,用于身份验证、授权、加密和会话管理等安全操作。Shiro框架的主要目标是提供易于使用的安全功能,同时保持灵活性和可扩展性。Shiro框架具有以下特点:

  • 易于使用:Shiro提供了简单的API,可以轻松地实现安全操作。
  • 灵活性:Shiro框架可以与任何应用程序集成,无论是大型企业应用程序还是小型Web应用程序。
  • 可扩展性:Shiro框架可以根据需要进行扩展,以满足不同的安全需求。
  • 广泛的应用场景:Shiro框架可以用于各种应用程序,包括Web应用程序、桌面应用程序、移动应用程序等。

了解一些框架概念

  • Subject(用户):Shiro中代表当前正在执行操作的用户,包含身份信息、角色信息、权限信息等。
  • SecurityManager(安全管理器):Shiro的核心组件,负责协调各个组件的工作,实现身份验证、授权、会话管理等安全功能。
  • Realms(数据源):Realm是Shiro框架中的一个组件,用于从数据源中获取用户信息、角色信息和权限信息,是Shiro进行身份验证和授权的核心组件。
  • Authenticator(认证器):用于对用户进行身份验证的组件,将用户提供的身份信息与数据源中的信息进行比对,判断用户是否具有访问资源的权限。
  • Authorizer(授权器):用于对用户进行授权的组件,根据用户的身份和角色信息判断用户是否有访问资源的权限。
  • SessionManager(会话管理器):用于管理用户会话信息的组件,包括会话创建、销毁、过期、续期等操作。
  • CacheManager(缓存管理器):用于缓存认证授权信息的组件,可以提高系统的性能和可扩展性。
  • Cryptography(加密组件):提供了加密解密的工具包,可以对用户的敏感信息进行加密处理,保证用户信息的安全性。

使用步骤

引入Shiro依赖

首先,需要将Shiro框架添加到您的项目中,可以通过Maven或手动下载Shiro库来实现。如果你使用Maven,只需将以下依赖项添加到您的pom.xml文件中:

<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-core</artifactId>
  <version>1.8.1</version>
</dependency>

配置Shiro

在你的应用程序中,需要配置Shiro框架以启用身份验证和授权功能。可以使用Shiro提供的INI文件或编程方式进行配置。以下是使用INI文件进行配置的示例:

# shiro.ini
[main]
# 指定Realm
authcRealm = org.apache.shiro.realm.jdbc.JdbcRealm

# 指定数据源
jdbcRealm.dataSource = com.mysql.jdbc.jdbc2.optional.MysqlDataSource
jdbcRealm.dataSource.user = root
jdbcRealm.dataSource.password = mypassword
jdbcRealm.dataSource.URL = jdbc:mysql://localhost:3306/test

# 指定加密算法
jdbcRealm.credentialsMatcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
jdbcRealm.credentialsMatcher.hashAlgorithmName = SHA-256

# 配置默认的安全管理器
securityManager.realms = $authcRealm

在上面的配置中,我们指定了使用JdbcRealm进行身份验证,使用MySQL数据源进行数据库连接,并使用SHA-256算法进行密码加密。最后,我们将安全管理器配置为使用默认的Realm。

当然在项目中我们一般使用xml文件来配置:(配置文件一般不需要仔细研究,你需要什么功能上网搜索添加即可,这里只是提供我用到的配置功能)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop.xsd
  http://www.springframework.org/schema/tx
  http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--组件扫描器-->
    <context:component-scan base-package="main.com.em.realm" />

    <!-- shiro过滤器bean,id要和web.xml中filter-name一致 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />

        <!-- 要求登录时的链接(登录页面地址),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面 -->
        <!--<property name="loginUrl" value="/admin/show"></property>-->

        <property name="filterChainDefinitions">
            <value>
                #这里相当于ini配置文件中的[urls]
                #url=拦截器[参数],拦截器
                # /techer/** = authc, perms[document:read]
                # 如果用户没有该角色,然后访问该 路径 会报 401错误

                /admin/** = authc, roles[admin]
                /techer/** = authc, roles[teacher]
                /student/** = authc, roles[student]

                # 当访问login时,不用进行认证(anon表示匿名)
                /login = anon

                /logout = logout

                # 配置静态资源可以匿名访问
                /css/** = anon
                /js/** = anon
                /images/** = anon
                /fonts/** = anon

                # 除了上面额 /login 可以匿名访问,其他路径都需要登录访问
                # 如果没登录,就访问其他路径会跳转到 /login 登录

                /** = authc
            </value>
        </property>
    </bean>


    <!-- 配置securityManager -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!--配置自定义Realm-->
        <!--loginRealm 使用扫描器扫描注册成功了-->
        <property name="realm" ref="loginRealm" />
        <!-- <property name="sessionMode" value="native"/> -->
    </bean>
    <!-- 生命周期 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />


    <!-- 启用shiro注解 -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>


</beans>

实现登录

需要实现登录功能。以下是一个简单的示例:

// 获取当前用户
Subject currentUser = SecurityUtils.getSubject();

// 将用户名和密码封装为UsernamePasswordToken对象
UsernamePasswordToken token = new UsernamePasswordToken(username, password);

// 执行登录操作
try {
    currentUser.login(token);
} catch (AuthenticationException e) {
    // 登录失败
}

在项目中一般封装到Controller控制层来写登录:

@RequestMapping(value = "/login", method = {RequestMethod.POST})
    public String login(User user) throws Exception {

        //Shiro实现登录
        UsernamePasswordToken token = new UsernamePasswordToken(user.getId(),user.getPassword());
        Subject subject = SecurityUtils.getSubject();

        //如果获取不到用户名就是登录失败,但登录失败的话,会直接抛出异常
        subject.login(token);

        if (subject.hasRole("admin")) {
            return "redirect:/admin/showRoom";
        } else if (subject.hasRole("ordinary")) {
            return "redirect:/ordinary/showRoom";
        }

        return "/login";
    }

login()方法用于处理登录表单的提交。该方法使用@RequestMapping注解指定了请求的路径和请求方法,即POST请求的/login路径。在该方法中,我们首先将用户提交的用户名和密码封装成UsernamePasswordToken对象,然后调用SecurityUtils.getSubject()方法获取Subject对象,并调用subject.login(token)方法进行身份验证。如果身份验证通过,则根据用户的角色跳转到不同的页面。

在该方法中,我们使用了Shiro框架提供的Subject对象进行身份认证和授权。Subject是Shiro框架中的核心对象,用于表示当前用户的身份信息和会话状态。我们通过SecurityUtils.getSubject()方法获取Subject对象,然后调用subject.login(token)方法进行身份验证。如果验证通过,则可以调用subject.hasRole()方法判断用户是否具有某个角色,从而进行权限控制。

实现 Realm

在使用Shiro框架进行授权登录的时候需要实现两个方法一个用来验证身份,另一个授权登录,以下提供我在项目里写的逻辑:

@Component
public class LoginRealm extends AuthorizingRealm{

    @Resource(name = "userServiceImpl")
    private UserService userService;

    @Resource(name = "roleServiceImpl")
    private RoleService roleService;


    /**
     * 获取身份信息,我们可以在这个方法中,从数据库获取该用户的权限和角色信息
     *     当调用权限验证时,就会调用此方法
     */
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        String id = (String) getAvailablePrincipal(principalCollection);

        String role = null;

        try {
            User user = userService.findUserById(id);
            Integer i=user.getRole();
            //获取角色对象
            role=roleService.findNameById(i);
            //role = roleService.findRoleById(user.getRole());
        } catch (Exception e) {
            e.printStackTrace();
        }
        //通过用户名从数据库获取权限/角色信息
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Set<String> set = new HashSet<String>();
        if (role != null) {
            set.add(role);
            info.setRoles(set);
        }

        return info;
    }

    /**
     * 在这个方法中,进行身份验证
     *         login时调用
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //用户名
        String id = (String) token.getPrincipal();
        //密码
        String password = new String((char[])token.getCredentials());

        User user = null;
        try {
            user = userService.findUserById(id);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (user == null) {
            //没有该用户名
            throw new UnknownAccountException();
        } else if (!password.equals(user.getPassword())) {
            //密码错误
            throw new IncorrectCredentialsException();
        }

        //身份验证通过,返回一个身份信息
        AuthenticationInfo loginInfo = new SimpleAuthenticationInfo(id,password,getName());

        return loginInfo;
    }
}

代码逻辑流程

代码逻辑流程:

首先,这个类继承了Shiro框架中的AuthorizingRealm类,该类提供了身份验证和授权的基本实现。

在该类中,我们定义了两个依赖注入的成员变量:UserServiceRoleService。这两个服务类分别用于用户信息和角色信息的获取。

doGetAuthorizationInfo()方法用于获取用户的权限和角色信息。在该方法中,我们首先从PrincipalCollection中获取用户的id,然后根据该id从数据库中获取用户信息和角色信息。最后,将角色信息封装成SimpleAuthorizationInfo对象并返回。

doGetAuthenticationInfo()方法用于进行身份验证。在该方法中,我们首先从AuthenticationToken中获取用户名和密码,然后根据用户名从数据库中获取用户信息。如果用户不存在,则抛出UnknownAccountException异常;如果密码不正确,则抛出IncorrectCredentialsException异常。如果身份验证通过,则将用户信息和密码封装成SimpleAuthenticationInfo对象并返回。

在Shiro框架中,身份验证和授权的流程如下:

  1. 用户进行登录操作,提交用户名和密码;
  2. Shiro框架调用doGetAuthenticationInfo()方法进行身份验证;
  3. 如果身份验证通过,则将用户信息保存在Shiro的会话管理器中,并返回页面;
  4. 用户进行其他操作时,比如访问受保护的资源,Shiro框架会调用doGetAuthorizationInfo()方法进行权限控制;
  5. 如果用户的角色和权限满足访问条件,则允许访问;否则,拒绝访问并跳转到相应的错误页面。

Shiro框架的原理

Shiro框架的核心是Subject对象,它代表了当前正在执行的用户。Subject对象包含了当前用户的身份信息、角色信息、权限信息等。在Shiro框架中,身份验证和授权是分开的。身份验证是指验证用户的身份是否正确,而授权是指授予用户访问资源的权限。Shiro框架可以使用不同的Realm来执行身份验证和授权操作。Realm是Shiro框架中的一个组件,它负责从数据源中获取用户信息、角色信息和权限信息。Shiro框架还提供了一些过滤器(Filter),用于在访问受保护的资源之前进行身份验证和授权检查。在Shiro框架中,安全管理器(SecurityManager)是一个关键组件,它负责协调身份验证、授权和会话管理等操作。

总体来说,Shiro框架提供了一个灵活、易于使用的安全框架,可以帮助开发人员轻松地实现身份验证和授权操作。通过使用Shiro框架,开发人员可以大大减少安全代码的编写量,并提高应用程序的安全性和可靠性。