jeesite用户登录功能解析

1 篇文章 0 订阅
1 篇文章 0 订阅

登录功能路径分析

一般是在浏览器中输入 http://localhost:8080/gznc:,页面就会自动跳转到登录页面。http://localhost:8080/gznc/a/login;JSESSIONID=3076c2dba40d45d0b18ce9ba7b482c9f

在spring-mvc.xml中,第82行中配置了无Controller的path-view的直接映射

<mvc:view-controller path="/" view-name="redirect:${web.view.index}"/>

首先看下web.view.index是什么值?

在jeesite.properties第211行,

web.view.index=/a

也就是说,我们直接访问http://localhost:8080/gznc,其实会被映射到http://localhost:8080/gznc/a

那么,/a是什么?在jeesite.properties第174行,

adminPath=/a

frontPath=/f

通过字面意思,可以知道,/a其实就是管理平台的路径,/f就是门户前台的地址。项目中通过/a和/f来区分前后台的请求路径。

http://localhost:8080/gznc/a这个地址对应的controller层方式是在 com.thinkgem.jeesite.modules.sys.web.LoginController类文件中,对应的方式为“index”.

/**
 * 登录成功,进入管理首页
 */
@RequiresPermissions("user")
@RequestMapping(value = "${adminPath}")
public String index(HttpServletRequest request, HttpServletResponse response) {
}

但是,直接在函数方法中打断点调试,输入http://localhost:8080/gznc/a,却会发现并不会进入这个方法。猜测是存在过滤器。在注解中,@RequiresPermissions(“user”) 标明进入这个方法需要user的权限。所以继续检查shiro配置文件 spring-context-shiro.xml。

<!-- Shiro权限过滤过滤器定义 -->
    <bean name="shiroFilterChainDefinitions" class="java.lang.String">
        <constructor-arg>
            <value>
                /static/** = anon
                /userfiles/** = anon
                ${adminPath}/cas = cas
                ${adminPath}/login = authc
                ${adminPath}/logout = logout
                ${adminPath}/** = user
                /act/editor/** = user
                /ReportServer/** = user
            </value>
        </constructor-arg>
    </bean>

    <!-- 安全认证过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" /><!-- 
        <property name="loginUrl" value="${cas.server.url}?service=${cas.project.url}${adminPath}/cas" /> -->
        <property name="loginUrl" value="${adminPath}/login" />
        <property name="successUrl" value="${adminPath}?login" />
        <property name="filters">
            <map>
                <entry key="cas" value-ref="casFilter"/>
                <entry key="authc" value-ref="formAuthenticationFilter"/>
            </map>
        </property>
        <property name="filterChainDefinitions">
            <ref bean="shiroFilterChainDefinitions"/>
        </property>
    </bean>

初次进入http://localhost:8080/gznc/a,因为还没有登录,所以没有user权限,就会跳转到 loginUrl路径,及 “${adminPath}/login”, 也就是 http://localhost:8080/gznc/a/login。由此,后台的登录path流程大致清楚了。

登录功能流转

http://localhost:8080/gznc/a/login对应的controller是com.thinkgem.jeesite.modules.sys.web.LoginController类文件,对应的方式为login。在方法中,可以看到对应的登录页面为 modules/sys/sysLogin;

/**
 * 管理登录
 */
@RequestMapping(value = "${adminPath}/login", method = RequestMethod.GET)
public String login(HttpServletRequest request, HttpServletResponse response, Model model) {
    Principal principal = UserUtils.getPrincipal();

    if (logger.isDebugEnabled()){
        logger.debug("login, active session size: {}", sessionDAO.getActiveSessions(false).size());
    }

    // 如果已登录,再次访问主页,则退出原账号。
    if (Global.TRUE.equals(Global.getConfig("notAllowRefreshIndex"))){
        CookieUtils.setCookie(response, "LOGINED", "false");
    }

    // 如果已经登录,则跳转到管理首页
    if(principal != null && !principal.isMobileLogin()){
        return "redirect:" + adminPath;
    }

    return "modules/sys/sysLogin";
}

在sysLogin.jsp中,主要是一个用户登录表单。

<form id="loginForm" class="form-signin" action="${ctx}/login" method="post">
        <label class="input-label" for="username">登录名</label>
        <input type="text" id="username" name="username" class="input-block-level required" value="${username}">
        <label class="input-label" for="password">密码</label>
        <input type="password" id="password" name="password" class="input-block-level required">
        <input class="btn btn-large btn-primary" type="submit" value="登 录"/>&nbsp;&nbsp;
    </form>

主要的目的是接收用户输入的用户名和密码,action指定了表单的提交方式为 post, 提交的路径为 ${ctx}/login
在taglib.jsp中,

<c:set var="ctx" value="${pageContext.request.contextPath}${fns:getAdminPath()}"/>

${ctx}其实就是 http//localhost:8080/gznc/a/。
${ctx}/login就是 http//localhost:8080/gznc/a/login。

com.thinkgem.jeesite.modules.sys.web.LoginController类文件中,http//localhost:8080/gznc/a/login POST的方式请求对应的方式就是 loginFail。

/**
     * 登录失败,真正登录的POST请求由Filter完成
     */
    @RequestMapping(value = "${adminPath}/login", method = RequestMethod.POST)
    public String loginFail(HttpServletRequest request, HttpServletResponse response, Model model) {
        Principal principal = UserUtils.getPrincipal();

        // 如果已经登录,则跳转到管理首页
        if(principal != null){
            return "redirect:" + adminPath;
        }

        String username = WebUtils.getCleanParam(request, FormAuthenticationFilter.DEFAULT_USERNAME_PARAM);
        boolean rememberMe = WebUtils.isTrue(request, FormAuthenticationFilter.DEFAULT_REMEMBER_ME_PARAM);
        boolean mobile = WebUtils.isTrue(request, FormAuthenticationFilter.DEFAULT_MOBILE_PARAM);
        String exception = (String)request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
        String message = (String)request.getAttribute(FormAuthenticationFilter.DEFAULT_MESSAGE_PARAM);

        if (StringUtils.isBlank(message) || StringUtils.equals(message, "null")){
            message = "用户或密码错误, 请重试.";
        }

        model.addAttribute(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM, username);
        model.addAttribute(FormAuthenticationFilter.DEFAULT_REMEMBER_ME_PARAM, rememberMe);
        model.addAttribute(FormAuthenticationFilter.DEFAULT_MOBILE_PARAM, mobile);
        model.addAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME, exception);
        model.addAttribute(FormAuthenticationFilter.DEFAULT_MESSAGE_PARAM, message);

        if (logger.isDebugEnabled()){
            logger.debug("login fail, active session size: {}, message: {}, exception: {}", 
                    sessionDAO.getActiveSessions(false).size(), message, exception);
        }

        // 非授权异常,登录失败,验证码加1。
        if (!UnauthorizedException.class.getName().equals(exception)){
            model.addAttribute("isValidateCodeLogin", isValidateCodeLogin(username, true, false));
        }

        // 验证失败清空验证码
        request.getSession().setAttribute(ValidateCodeServlet.VALIDATE_CODE, IdGen.uuid());

        // 如果是手机登录,则返回JSON字符串
        if (mobile){
            return renderString(response, model);
        }

        return "modules/sys/sysLogin";
    }

如果登录成功了,则跳转到 “redirect:” + adminPath,及
com.thinkgem.jeesite.modules.sys.web.LoginController类文件中的“index”方法。

/**
 * 登录成功,进入管理首页
 */
@RequiresPermissions("user")
@RequestMapping(value = "${adminPath}")
public String index(HttpServletRequest request, HttpServletResponse response) {
    Principal principal = UserUtils.getPrincipal();

    // 登录成功后,验证码计算器清零
    isValidateCodeLogin(principal.getLoginName(), false, true);

    if (logger.isDebugEnabled()){
        logger.debug("show index, active session size: {}", sessionDAO.getActiveSessions(false).size());
    }

    // 如果已登录,再次访问主页,则退出原账号。
    if (Global.TRUE.equals(Global.getConfig("notAllowRefreshIndex"))){
        String logined = CookieUtils.getCookie(request, "LOGINED");
        if (StringUtils.isBlank(logined) || "false".equals(logined)){
            CookieUtils.setCookie(response, "LOGINED", "true");
        }else if (StringUtils.equals(logined, "true")){
            UserUtils.getSubject().logout();
            return "redirect:" + adminPath + "/login";
        }
    }

    // 如果是手机登录,则返回JSON字符串
    if (principal.isMobileLogin()){
        if (request.getParameter("login") != null){
            return renderString(response, principal);
        }
        if (request.getParameter("index") != null){
            return "modules/sys/sysIndex";
        }
        return "redirect:" + adminPath + "/login";
    }

    return "modules/sys/sysIndex";
}

然后进入首页 sysIndex

登录信息验证

com.thinkgem.jeesite.modules.sys.web.LoginController类文件中,http//localhost:8080/gznc/a/login POST的方式请求对应的方法 loginFail 中,并看不到用户登录信息验证的过程,这是因为shiro的登录功能在${adminPaht}/login中加入了过滤器。
这个过滤器配置在spring-context-shiro.xml文件里。

<!-- Shiro权限过滤过滤器定义 -->
<bean name="shiroFilterChainDefinitions" class="java.lang.String">
    <constructor-arg>
        <value>
            /static/** = anon
            /userfiles/** = anon
            ${adminPath}/cas = cas
            ${adminPath}/login = authc
            ${adminPath}/logout = logout
            ${adminPath}/** = user
            /act/editor/** = user
            /ReportServer/** = user
        </value>
    </constructor-arg>
</bean>

<!-- 安全认证过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager" />
    <property name="loginUrl" value="${adminPath}/login" />
    <property name="successUrl" value="${adminPath}?login" />
    <property name="filters">
        <map>
            <entry key="cas" value-ref="casFilter"/>
            <entry key="authc" value-ref="formAuthenticationFilter"/>
        </map>
    </property>
    <property name="filterChainDefinitions">
        <ref bean="shiroFilterChainDefinitions"/>
    </property>
</bean>

配置中的loginUrl就是指 登录界面
successUrl就是指登录成功的url访问的位置。
在此处,给${adminPath}/login=authc。指定了验证权限名为authc的过滤器。 authc对应的filter为formAuthenticationFilter。

所以整个登录逻辑为:如果任何地方未登录,则访问登录页面,提交时先通过formAuthenticationFilter过滤器,验证账号密码,如果验证通过,则访问主页。

在formAuthenticationFilter中,首先会获取表单中的用户名称和密码,然后传给createToken函数,生成一个自定义的token给SystemAuthorizingRealm中的doGetAuthenticationInfo验证。在SystemAutnorizingRealm中有systemService的实例,该实例中的userDao能取出数据库中的name和password。接着由这两个密码生成SimpleAuthenticationInfo,再由info中的逻辑来验证。

FormAuthenticationFilter createToken
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
    String username = getUsername(request);
    String password = getPassword(request);
    if (password==null){
        password = "";
    }
    boolean rememberMe = isRememberMe(request);
    String host = StringUtils.getRemoteAddr((HttpServletRequest)request);
    String captcha = getCaptcha(request);
    boolean mobile = isMobileLogin(request);
    return new UsernamePasswordToken(username, password.toCharArray(), rememberMe, host, captcha, mobile);
}
AuthenticatingFilter类 executeLogin

FormAuthenticationFilter的父类过滤器执行方法 executeLogin调用 createToken.

protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
    AuthenticationToken token = createToken(request, response);
    if (token == null) {
        String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
                "must be created in order to execute a login attempt.";
        throw new IllegalStateException(msg);
    }
    try {
        Subject subject = getSubject(request, response);
        subject.login(token);
        return onLoginSuccess(token, subject, request, response);
    } catch (AuthenticationException e) {
        return onLoginFailure(token, e, request, response);
    }
}

在executeLogin中,subject.login(token)会调用SystemAuthorizingRealm类中的doGetAuthenticationInfo方法。

/**
     * 认证回调函数, 登录时调用
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;

        int activeSessionSize = getSystemService().getSessionDao().getActiveSessions(false).size();
        if (logger.isDebugEnabled()){
            logger.debug("login submit, active session size: {}, username: {}", activeSessionSize, token.getUsername());
        }

        // 校验登录验证码
        if (LoginController.isValidateCodeLogin(token.getUsername(), false, false)){
            Session session = UserUtils.getSession();
            String code = (String)session.getAttribute(ValidateCodeServlet.VALIDATE_CODE);
            if (token.getCaptcha() == null || !token.getCaptcha().toUpperCase().equals(code)){
                throw new AuthenticationException("msg:验证码错误, 请重试.");
            }
        }

        // 校验用户名密码
        User user = getSystemService().getUserByLoginName(token.getUsername());
        if (user != null) {
            if (Global.NO.equals(user.getLoginFlag())){
                throw new AuthenticationException("msg:该已帐号禁止登录.");
            }
            byte[] salt = Encodes.decodeHex(user.getPassword().substring(0,16));
            return new SimpleAuthenticationInfo(new Principal(user, token.isMobileLogin()), 
                    user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName());
        } else {
            return null;
        }
    }

在这个方法方法中,实现用户名密码的认证,并返回认证信息。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值