English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

springmvc와 shiro 로그인 권한 예제 코드 통합

일반적인 로그인 프로세스에는 사용자 이름이 없음, 비밀번호가 틀림,��인증 번호가 틀림 등이 있습니다...

시로를 통합한 후, 애플리케이션의 외부 접근 권한 및 접근 제어는 시로에 의해 관리됩니다.

시로는 두 가지 주요 기능을 제공합니다: 인증(Authentication)과 권한 부여(Authorization); 인증의 역할은 자신이 접근할 수 있는지 증명하는 것으로, 일반적으로 사용자 이름과 비밀번호를 사용합니다. 권한 부여의 역할은 누가 어떤 자원에 접근할 수 있는지 결정하는 것으로, 개발자의 사용자 역할 권한 시스템을 통해 제어됩니다.

시로의 세션 관리와 캐시 관리는 본 문서의 범위 내에 있지 않습니다.

스프링 MVC와 시로의 통합을 소개하기 위해 로그인 실패 처리流程을 통해 설명합니다.

프로젝트 의존성:

의존성 이름  버전
spring 4.1.4.RELEASE
shiro 1.2.2
self4j 1.7.5
log4j 1.2.17

web.xml에서 shiro를 설정합니다.

<filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
      <param-name>targetFilterLifecycle</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

새로운 spring-context-shiro.xml에서 shiro 관련 정보를 설정하고 spring를 통해 로드합니다.

<?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"
  xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd"
  default-lazy-init="true">
  <description>Shiro Configuration</description>
  <!-- 보안 인증 필터 -->
  <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager" />
    <property name="loginUrl" value="/sys/login" />
    <property name="successUrl" value="/sys" />
    <property name="filters">
      <map>
          <!--사용자 정의 로그인 인증 필터-->
        <entry key="authc" value-ref="formAuthenticationFilter" />
      </map>
    </property>
    <property name="filterChainDefinitions">
      <value>
        /sys/login = authc
        /sys/logout = logout
        /sys/** = user
      </value>
    </property>
  </bean>
  <!-- Shiro 주요 비즈니스 객체 정의 -->
  <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="systemAuthorizingRealm" />
    <property name="cacheManager" ref="shiroCacheManager" />
  </bean>
  <!-- 세션 ID 생성기 -->
  <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator">/>
  <!-- 세션 관리자, 세션 타임아웃 및 저장 설정 -->
  <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    <!-- 전체 세션 타임아웃 시간(단위 밀리초), 기본30분 -->
    <property name="globalSessionTimeout" value="1800000" />
    <property name="sessionDAO" ref="sessionDAO"/>
  </bean>
  <!-- 세션 검증 스케줄러, 매30분마다 검증 수행 -->
  <!-- <bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler"> -->
  <bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler">
    <property name="interval" value="1800000"/>
    <property name="sessionManager" ref="sessionManager"/>
  </bean>
  <!-- sessionDAO가 인증 정보를 저장합니다. -->
  <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
    <property name="activeSessionsCacheName" value="shiro-activeSessionCache />
    <property name="cacheManager" ref="shiroCacheManager" />
    <property name="sessionIdGenerator" ref="sessionIdGenerator"/>
  </bean>
  <!-- 사용자 권한 정보 캐시, EhCache를 사용합니다. -->
  <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
    <property name="cacheManager" ref="cacheManager" />
  </bean>
  <!-- Shiro 생명주기 처리기 -->
  <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
  <!-- AOP 스타일 메서드 수준 권한 검사 -->
  <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
    <property name="proxyTargetClass" value="true" />
  </bean>
  <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager" />
  </bean>
</beans>

FormAuthenticationFilter.java 새로운 로그인 인증 필터를 만듭니다.

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.stereotype.Service;
/**
 * 양식 인증 검증(인증 코드 포함) 필터 클래스*/
@Service
public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter {
  public static final String DEFAULT_CAPTCHA_PARAM = "validateCode";
  private String captchaParam = DEFAULT_CAPTCHA_PARAM;
  public String getCaptchaParam() {
    return captchaParam;
  }
  protected String getCaptcha(ServletRequest request) {
    return WebUtils.getCleanParam(request, getCaptchaParam());
  }
  protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
    String username = getUsername(request);
    String password = getPassword(request);
    String locale = request.getParameter("locale");
    if (password == null) {
      password = "";
    }
    boolean rememberMe = isRememberMe(request);
    String host = getHost(request);
    String captcha = getCaptcha(request);
    return new UsernamePasswordToken(username, password.toCharArray(), locale, rememberMe, host, captcha);
  }
}

UsernamePasswordToken 클래스를 새로 만듭니다 UsernamePasswordToken.java

package com.chunhui.webservice.modules.sys.security;
/**
 * 사용자와 비밀번호(인증 코드 포함) 토큰 클래스*/
public class UsernamePasswordToken extends org.apache.shiro.authc.UsernamePasswordToken {
  private static final long serialVersionUID = 1L;
  private String captcha;
  private String locale;
  public String getCaptcha() {
    return captcha;
  }
  public void setCaptcha(String captcha) {
    this.captcha = captcha;
  }
  public String getLocale() {
    return locale;
  }
  public void setLocale(String locale) {
    this.locale = locale;
  }
  public UsernamePasswordToken() {
    super();
  }
  public UsernamePasswordToken(String username, char[] password, boolean rememberMe, String host, String captcha) {
    super(username, password, rememberMe, host);
    this.captcha = captcha;
  }
  public UsernamePasswordToken(String username, char[] password, String locale, boolean rememberMe, String host, String captcha) {
    super(username, password, rememberMe, host);
    this.captcha = captcha;
    this.locale = locale;
  }
}

최종은 인증 구현 클래스 SystemAuthorizationRealm:

package com.chunhui.webservice.modules.sys.security;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import com.chunhui.webservice.common.utils.EmployeeType;
import com.chunhui.webservice.common.utils.VertifyStatus;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;
import com.chunhui.webservice.common.servlet.ValidateCodeServlet;
import com.chunhui.webservice.common.utils.SpringContextHolder;
import com.chunhui.webservice.modules.sys.entity.Employee;
import com.chunhui.webservice.modules.sys.entity.Menu;
import com.chunhui.webservice.modules.sys.service.SystemService;
import com.chunhui.webservice.modules.sys.utils.SystemUtils;
import com.chunhui.webservice.modules.sys.web.LoginController;
/**
 * 시스템 보안 인증 구현 클래스*/
@Service
@DependsOn({ "employeeDao", "roleDao", "menuDao" })
public class SystemAuthorizingRealm extends AuthorizingRealm {
  private SystemService systemService;
  /**
   * 인증 콜백 함수, 로그인 시 호출
   */
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
    UsernamePasswordToken 토큰 = (UsernamePasswordToken) authcToken;
// 인증 코드�断정
    Session 세션 = SecurityUtils.getSubject().getSession();
    // 독립된 세션 세션이 응답 시간을 설정 session.setTimeout(60000);
    String 코드 = (String) 세션.getAttribute(ValidateCodeServlet.VALIDATE_CODE);
    if (token.getCaptcha() == null || !token.getCaptcha().toUpperCase().equals(code)) {
      throw new CaptchaException("验证码错误!");
    }
     //如果帐号不存在,输出
    //throw new UnknownAccountException();
    //如果帐号被禁用,输出      
    //throw new DisabledAccountException();
    //保存登录时选择的语言
    SecurityUtils.getSubject().getSession().setAttribute("locale", token.getLocale());
    try{
      SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(new Principal(employee), employee.getPassword(), getName());
      return info;
    }catch (Throwable t){
      t.printStackTrace();
      throw new AuthenticationException();
    }
  }/**
   * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用
   */
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    Principal principal = (Principal) getAvailablePrincipal(principals);
    Employee employee = getSystemService().getEmployeeByName(principal.getUsername());
    if (employee != null) {
      SystemUtils.putCache("employee", employee);
      SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
      List<Menu> list = SystemUtils.getMenuList();
      for (Menu menu : list) {
        if (StringUtils.isNotBlank(menu.getPermission())) {
          // 添加基于Permission的权限信息
          for (String permission : StringUtils.split(menu.getPermission(), ",")) {
            info.addStringPermission(permission);
          }
        }
      }
      // 更新登录IP和时间
      getSystemService().updateEmployeeLoginInfo(employee.getId());
      return info;
    } else {
      return null;
    }
  }
  /**
   * 清空用户关联权限认证,待下次使用时重新加载
   */
  public void clearCachedAuthorizationInfo(String principal) {
    SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
    clearCachedAuthorizationInfo(principals);
  }
  /**
   * 清空所有关联认证
   */
  public void clearAllCachedAuthorizationInfo() {
    Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
    if (cache != null) {
      for (Object key : cache.keys()) {
        cache.remove(key);
      }
    }
  }
  /**
   * 获取系统业务对象
   */
  public SystemService getSystemService() {
    if (systemService == null) {
      systemService = SpringContextHolder.getBean(SystemService.class);
    }
    return systemService;
  }
  /**
   * 인증 사용자 정보
   */
  public static class Principal implements Serializable {
    private static final long serialVersionUID = 1L;
    private String id;
    private String username;
    private String realname;
    private Map<String, Object> cacheMap;
    public Principal(Employee employee) {
      this.id = employee.getId();
      this.username = employee.getUsername();
      this.realname = employee.getRealname();
    }
    public String getId() {
      return id;
    }
    public String getUsername() {
      return username;
    }
    public String getRealname() {
      return realname;
    }
    public Map<String, Object> getCacheMap() {
      if (cacheMap == null) {
        cacheMap = new HashMap<String, Object>();
      }
      return cacheMap;
    }
  }
}

그렇다면 JSP 페이지에서는 로그인 예외의 구체적인 예외 유형을 가져와서 오류 원인을 페이지에 표시할 수 있습니다

<%String error = (String) request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);%>
       <c:set var="exp_type" value="<%=error %>"/>
      <c:set var="tips" value=""></c:set>
      <c:if test="${fn:contains(exp_type,'CaptchaException')}">
        <c:set var="tips" value="인증 코드가 틀립니다"></c:set>
      </c:if>
      <c:if test="${fn:contains(exp_type,'FailVertifyException')}">
        <c:set var="tips" value="이 계정의 검토가 통과되지 않았습니다. 로그인이 허용되지 않습니다!"></c:set>
      </c:if>
      <c:if test="${fn:contains(exp_type,'NotVertifyException')}">
        <c:set var="tips" value="이 계정이 검토 중입니다... 로그인이 허용되지 않습니다!"></c:set>
      </c:if>
      <c:if test="${fn:contains(exp_type,'UnknownAccountException')}">
        <c:set var="tips" value="계정이 존재하지 않습니다!"></c:set>
      </c:if>
      <c:if test="${fn:contains(exp_type,'DisabledAccountException')}">
        <c:set var="tips" value="계정이 로그인이 허용되지 않습니다!"></c:set>
      </c:if>
      <c:if test="${fn:contains(exp_type,'IncorrectCredentialsException')}">
        <c:set var="tips" value="비밀번호가 틀립니다!"></c:set>
      </c:if>

이것이 본 문서의 전체 내용입니다. 여러분의 학습에 도움이 되길 바라며, 많은 지지를 부탁드립니다. 또한, 외침 교육의 많은 지지를 부탁드립니다.

선언: 본 문서의 내용은 인터넷에서 가져왔으며, 원작자에게 속합니다. 인터넷 사용자가 자발적으로 기여하고 업로드한 내용으로, 이 사이트는 소유권을 가지지 않으며, 인공적으로 편집한 것이 아니며, 관련 법적 책임을 부담하지 않습니다. 저작권 침해가 의심되는 내용이 있다면, 이메일을 notice#w로 보내 주시기 바랍니다.3codebox.com(보내는 이메일에서 #을 @으로 변경하십시오. 신고하고 관련 증거를 제공하시면, 사실이 확인되면 해당 사이트는 즉시 저작권 침해 내용을 삭제합니다。)