文档章节

shiro 登录授权过程详细解析

 丁正霞
发布于 2017/03/23 15:00
字数 2936
阅读 144
收藏 0

web.xml上会做如下配置,以便把spring配置的shiro bean引入到filter当中使用 DelegatingFilterProxy类的具体作用和使用方法请自行百度,这儿就不在啰嗦了。

<!-- Apache 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>

现在我们进入到shiro的spring配置文件当中。

必须配置这个bean 而且名字也必须和我们在web.xml的filter名字一样。当然可以用另外的targetname的方式,这就不在讲这个

<!-- 安全认证过滤器 -->
	<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}?index" />
		<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>

我们接下来看DelegatingFilterProxy怎么使用这个配置的bean的 spring 会把我们定义在xml里面的bean注入到这个对象当中

	private volatile Filter delegate;

以下一段是DelegatingFilterProxy.doFilter的内容

@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		// Lazily initialize the delegate if necessary.
		Filter delegateToUse = this.delegate;
		if (delegateToUse == null) {
			synchronized (this.delegateMonitor) {
				if (this.delegate == null) {
					WebApplicationContext wac = findWebApplicationContext();
					if (wac == null) {
						throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
					}
					this.delegate = initDelegate(wac);
				}
				delegateToUse = this.delegate;
			}
		}

		// Let the delegate perform the actual doFilter operation.
		//前面的代码就不详细分析了。主要看以下这个方法
		invokeDelegate(delegateToUse, request, response, filterChain);
	}

这儿就开始调用我们的spring bean 的doFilter方法了,从这儿我们可以看到,我们注入的spring bean必须是Filter类型的

/**
	 * Actually invoke the delegate Filter with the given request and response.
	 * @param delegate the delegate Filter
	 * @param request the current HTTP request
	 * @param response the current HTTP response
	 * @param filterChain the current FilterChain
	 * @throws ServletException if thrown by the Filter
	 * @throws IOException if thrown by the Filter
	 */
	protected void invokeDelegate(
			Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		delegate.doFilter(request, response, filterChain);
	}

以前就是解析DelegatingFilterProxy到我们bean的执行方式,下面我们开始看shiro的ShiroFilterFactoryBean是怎么样执行登录授权的。

从这我们可以看到这个bean是一个factorybean 那我们就直接看他的getObject方法就可以了

public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor

以下是getObject的方法

/**
     * Lazily creates and returns a {@link AbstractShiroFilter} concrete instance via the
     * {@link #createInstance} method.
     *
     * @return the application's Shiro Filter instance used to filter incoming web requests.
     * @throws Exception if there is a problem creating the {@code Filter} instance.
     */
    public Object getObject() throws Exception {
        if (instance == null) {
            //我们看下创建的方法
            instance = createInstance();
        }
        return instance;
    }

以下是创建shiro的Filter方法

/**
     * This implementation:
     * <ol>
     * <li>Ensures the required {@link #setSecurityManager(org.apache.shiro.mgt.SecurityManager) securityManager}
     * property has been set</li>
     * <li>{@link #createFilterChainManager() Creates} a {@link FilterChainManager} instance that reflects the
     * configured {@link #setFilters(java.util.Map) filters} and
     * {@link #setFilterChainDefinitionMap(java.util.Map) filter chain definitions}</li>
     * <li>Wraps the FilterChainManager with a suitable
     * {@link org.apache.shiro.web.filter.mgt.FilterChainResolver FilterChainResolver} since the Shiro Filter
     * implementations do not know of {@code FilterChainManager}s</li>
     * <li>Sets both the {@code SecurityManager} and {@code FilterChainResolver} instances on a new Shiro Filter
     * instance and returns that filter instance.</li>
     * </ol>
     *
     * @return a new Shiro Filter reflecting any configured filters and filter chain definitions.
     * @throws Exception if there is a problem creating the AbstractShiroFilter instance.
     */
    protected AbstractShiroFilter createInstance() throws Exception {

        log.debug("Creating Shiro Filter instance.");

        SecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            String msg = "SecurityManager property must be set.";
            throw new BeanInitializationException(msg);
        }

        if (!(securityManager instanceof WebSecurityManager)) {
            String msg = "The security manager does not implement the WebSecurityManager interface.";
            throw new BeanInitializationException(msg);
        }

        FilterChainManager manager = createFilterChainManager();

        //Expose the constructed FilterChainManager by first wrapping it in a
        // FilterChainResolver implementation. The AbstractShiroFilter implementations
        // do not know about FilterChainManagers - only resolvers:
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);

        //Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
        //FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class
        //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
        //injection of the SecurityManager and FilterChainResolver:
        //从这我们可以看到是创建了一个SpringShiroFilter
        return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }

当然我们可以看一下bean后置处理器设置一些几本属性的方法

//设置登录的  url
private void applyLoginUrlIfNecessary(Filter filter) {
        String loginUrl = getLoginUrl();
        if (StringUtils.hasText(loginUrl) && (filter instanceof AccessControlFilter)) {
            AccessControlFilter acFilter = (AccessControlFilter) filter;
            //only apply the login url if they haven't explicitly configured one already:
            String existingLoginUrl = acFilter.getLoginUrl();
            if (AccessControlFilter.DEFAULT_LOGIN_URL.equals(existingLoginUrl)) {
                acFilter.setLoginUrl(loginUrl);
            }
        }
    }

//设置登录成功跳转的url
    private void applySuccessUrlIfNecessary(Filter filter) {
        String successUrl = getSuccessUrl();
        if (StringUtils.hasText(successUrl) && (filter instanceof AuthenticationFilter)) {
            AuthenticationFilter authcFilter = (AuthenticationFilter) filter;
            //only apply the successUrl if they haven't explicitly configured one already:
            String existingSuccessUrl = authcFilter.getSuccessUrl();
            if (AuthenticationFilter.DEFAULT_SUCCESS_URL.equals(existingSuccessUrl)) {
                authcFilter.setSuccessUrl(successUrl);
            }
        }
    }

    private void applyUnauthorizedUrlIfNecessary(Filter filter) {
        String unauthorizedUrl = getUnauthorizedUrl();
        if (StringUtils.hasText(unauthorizedUrl) && (filter instanceof AuthorizationFilter)) {
            AuthorizationFilter authzFilter = (AuthorizationFilter) filter;
            //only apply the unauthorizedUrl if they haven't explicitly configured one already:
            String existingUnauthorizedUrl = authzFilter.getUnauthorizedUrl();
            if (existingUnauthorizedUrl == null) {
                authzFilter.setUnauthorizedUrl(unauthorizedUrl);
            }
        }
    }

//配置全局变量
    private void applyGlobalPropertiesIfNecessary(Filter filter) {
        applyLoginUrlIfNecessary(filter);
        applySuccessUrlIfNecessary(filter);
        applyUnauthorizedUrlIfNecessary(filter);
    }

    /**
     * Inspects a bean, and if it implements the {@link Filter} interface, automatically adds that filter
     * instance to the internal {@link #setFilters(java.util.Map) filters map} that will be referenced
     * later during filter chain construction.
     */
    //初始化
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Filter) {
            log.debug("Found filter chain candidate filter '{}'", beanName);
            Filter filter = (Filter) bean;
            applyGlobalPropertiesIfNecessary(filter);
            getFilters().put(beanName, filter);
        } else {
            log.trace("Ignoring non-Filter bean '{}'", beanName);
        }
        return bean;
    }

接着我们看SpringShiroFilter.doFilter方法,这个类本身没有实现doFilter,是由他父类的父类实现的OncePerRequestFilter

/**
     * This {@code doFilter} implementation stores a request attribute for
     * "already filtered", proceeding without filtering again if the
     * attribute is already there.
     *
     * @see #getAlreadyFilteredAttributeName
     * @see #shouldNotFilter
     * @see #doFilterInternal
     */
    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        //如果当前filter正在执行,就不在执行它,执行下一个filter
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
            filterChain.doFilter(request, response);
        } else //noinspection deprecation
            if (/* added in 1.2: */ !isEnabled(request, response) ||
                /* retain backwards compatibility: */ shouldNotFilter(request) ) {
            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
                    getName());
    //当前filter不支持,执行下一个filter
            filterChain.doFilter(request, response);
        } else {
        //执行当前filter
            // Do invoke this filter...
            log.trace("Filter '{}' not yet executed.  Executing now.", getName());
        //设置执行状态,避免多次提交问题
          request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
            //授权过程在这儿进行  我们接下来看下面的内容
                doFilterInternal(request, response, filterChain);
            } finally {
                // Once the request has finished, we're done and we don't
                // need to mark as 'already filtered' any more.
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }

doFilterInternal这个方法SpringShiroFilter它本身也没有实现,是由它的父类实现的 AbstractShiroFilter.doFilterInternal

protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
            throws ServletException, IOException {

        Throwable t = null;

        try {
        //对request,response做一些初始化工作
            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
//创建一个subject
            final Subject subject = createSubject(request, response);

            //noinspection unchecked
            
            //执行授权
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    updateSessionLastAccessTime(request, response);
                    executeChain(request, response, chain);
                    return null;
                }
            });
        } catch (ExecutionException ex) {
            t = ex.getCause();
        } catch (Throwable throwable) {
            t = throwable;
        }

        if (t != null) {
            if (t instanceof ServletException) {
                throw (ServletException) t;
            }
            if (t instanceof IOException) {
                throw (IOException) t;
            }
            //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
            String msg = "Filtered request failed.";
            throw new ServletException(msg, t);
        }
    }

根据当前请求,获取配置的filter链,并执行

protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
       
        FilterChain chain = getExecutionChain(request, response, origChain);
        chain.doFilter(request, response);
    }
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;

        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        }

        FilterChain resolved = resolver.getChain(request, response, origChain);
        if (resolved != null) {
            log.trace("Resolved a configured FilterChain for the current request.");
            chain = resolved;
        } else {
            log.trace("No FilterChain configured for the current request.  Using the default.");
        }

        return chain;
    }

shiro 会默认初始化以下filter在org.apache.shiro.web.filter.mgt.DefaultFilter里面 ,会在初始化SpringShiroFilter的时候一并初始化,并且把用户自定义的Filter也加进来,有兴趣的可以查看ShiroFilterFactoryBean.createInstance()方法和postProcessBeforeInitialization(Object bean, String beanName)方法

    anon(AnonymousFilter.class),
    authc(FormAuthenticationFilter.class),
    authcBasic(BasicHttpAuthenticationFilter.class),
    logout(LogoutFilter.class),
    noSessionCreation(NoSessionCreationFilter.class),
    perms(PermissionsAuthorizationFilter.class),
    port(PortFilter.class),
    rest(HttpMethodPermissionFilter.class),
    roles(RolesAuthorizationFilter.class),
    ssl(SslFilter.class),
    user(UserFilter.class);

初始化工作我们就不在详述了,我们接下来看他怎么通过请求去匹配对应的filter并且进行处理的。

protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
        FilterChain chain = origChain;

        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        }

        FilterChain resolved = resolver.getChain(request, response, origChain);
        if (resolved != null) {
            log.trace("Resolved a configured FilterChain for the current request.");
            chain = resolved;
        } else {
            log.trace("No FilterChain configured for the current request.  Using the default.");
        }

        return chain;

这儿有一个小说明 在sping配置文件当我们会定义这样一个bean,这个bean就是url匹配模式和filter的对应关系,多个filter可以通过逗号分隔多个filter

	<!-- 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>

我们查看ShiroFilterFactoryBean.createInstance可以得出,shiro会把我们的url匹配模式和filter链放入一个map当中

private Map<String, NamedFilterList> filterChains; //key: chain name, value: chain

PathMatchingFilterChainResolver.getChain 这个方法的大概流程就是如果当前URL和模式匹配,就取出他的filter链,并返回,如果找不到匹配的,就返回空

public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }

        String requestURI = getPathWithinApplication(request);

        //the 'chain names' in this implementation are actually path patterns defined by the user.  We just use them
        //as the chain name for the FilterChainManager's requirements
        for (String pathPattern : filterChainManager.getChainNames()) {

            // If the path does match, then pass on to the subclass implementation for specific checks:
            if (pathMatches(pathPattern, requestURI)) {
                if (log.isTraceEnabled()) {
                    log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  " +
                            "Utilizing corresponding filter chain...");
                }
                //生成一个代理链
                return filterChainManager.proxy(originalChain, pathPattern);
            }
        }

        return null;
    }

上面解析完成。接着我们看执行的地方

       protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
            throws IOException, ServletException {
        FilterChain chain = getExecutionChain(request, response, origChain);
        //filter链获取完成,执行
        chain.doFilter(request, response);
    }

接下来我们就直接查看 authc(FormAuthenticationFilter.class) FormAuthenticationFilter的doFilter方法了,其他方法filter类似 FormAuthenticationFilter这个filter是负责授权管理的filter 具体调用过程,可以直接通过开发工具查看,这儿就不详述了,我们直接看授权的方法

protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //判断是否是登录请求
        if (isLoginRequest(request, response)) {
        //判断是不是POST请求
            if (isLoginSubmission(request, response)) {
                if (log.isTraceEnabled()) {
                    log.trace("Login submission detected.  Attempting to execute login.");
                }
                //执行登录
                return executeLogin(request, response);
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("Login page view.");
                }
                //allow them to see the login page ;)
                return true;
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +
                        "Authentication url [" + getLoginUrl() + "]");
            }

            saveRequestAndRedirectToLogin(request, response);
            return false;
        }
    }

登录的地方

protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
//创建token
        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);
        }
    //获取前面创建的subject
            Subject subject = getSubject(request, response);
    //执行登录    
            subject.login(token);
    //登录成功
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
    //登录失败    
            return onLoginFailure(token, e, request, response);
        }
    }

DelegatingSubject.login

public void login(AuthenticationToken token) throws AuthenticationException {
        clearRunAsIdentitiesInternal();
        //登录
        Subject subject = securityManager.login(this, token);

        PrincipalCollection principals;

        String host = null;

        if (subject instanceof DelegatingSubject) {
            DelegatingSubject delegating = (DelegatingSubject) subject;
            //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:
            principals = delegating.principals;
            host = delegating.host;
        } else {
            principals = subject.getPrincipals();
        }

        if (principals == null || principals.isEmpty()) {
            String msg = "Principals returned from securityManager.login( token ) returned a null or " +
                    "empty value.  This value must be non null and populated with one or more elements.";
            throw new IllegalStateException(msg);
        }
        this.principals = principals;
        this.authenticated = true;
        if (token instanceof HostAuthenticationToken) {
            host = ((HostAuthenticationToken) token).getHost();
        }
        if (host != null) {
            this.host = host;
        }
        Session session = subject.getSession(false);
        if (session != null) {
            this.session = decorate(session);
        } else {
            this.session = null;
        }
    }

DefaultSecurityManager 这个类的构造方法如下

public AuthenticatingSecurityManager() {
        super();
        //用它去实现真正的登录
        this.authenticator = new ModularRealmAuthenticator();
    }

DefaultSecurityManager.login

public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo info;
        try {
            //获取授权信息
            info = authenticate(token);
        } catch (AuthenticationException ae) {
            try {
                onFailedLogin(token, ae, subject);
            } catch (Exception e) {
                if (log.isInfoEnabled()) {
                    log.info("onFailedLogin method threw an " +
                            "exception.  Logging and propagating original AuthenticationException.", e);
                }
            }
            throw ae; //propagate
        }

        Subject loggedIn = createSubject(token, info, subject);

        onSuccessfulLogin(token, info, loggedIn);

        return loggedIn;
    }
/**
     * Delegates to the wrapped {@link org.apache.shiro.authc.Authenticator Authenticator} for authentication.
     */
    public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        return this.authenticator.authenticate(token);
    }

接下来我们就要看早构造函数里面初始化的那个对象他的授权方法

/**
     * Implementation of the {@link Authenticator} interface that functions in the following manner:
     * <ol>
     * <li>Calls template {@link #doAuthenticate doAuthenticate} method for subclass execution of the actual
     * authentication behavior.</li>
     * <li>If an {@code AuthenticationException} is thrown during {@code doAuthenticate},
     * {@link #notifyFailure(AuthenticationToken, AuthenticationException) notify} any registered
     * {@link AuthenticationListener AuthenticationListener}s of the exception and then propogate the exception
     * for the caller to handle.</li>
     * <li>If no exception is thrown (indicating a successful login),
     * {@link #notifySuccess(AuthenticationToken, AuthenticationInfo) notify} any registered
     * {@link AuthenticationListener AuthenticationListener}s of the successful attempt.</li>
     * <li>Return the {@code AuthenticationInfo}</li>
     * </ol>
     *
     * @param token the submitted token representing the subject's (user's) login principals and credentials.
     * @return the AuthenticationInfo referencing the authenticated user's account data.
     * @throws AuthenticationException if there is any problem during the authentication process - see the
     *                                 interface's JavaDoc for a more detailed explanation.
     */
    public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {

        if (token == null) {
            throw new IllegalArgumentException("Method argumet (authentication token) cannot be null.");
        }

        log.trace("Authentication attempt received for token [{}]", token);

        AuthenticationInfo info;
        try {
        //授权
            info = doAuthenticate(token);
            if (info == null) {
                String msg = "No account information found for authentication token [" + token + "] by this " +
                        "Authenticator instance.  Please check that it is configured correctly.";
                throw new AuthenticationException(msg);
            }
        } catch (Throwable t) {
            AuthenticationException ae = null;
            if (t instanceof AuthenticationException) {
                ae = (AuthenticationException) t;
            }
            if (ae == null) {
                //Exception thrown was not an expected AuthenticationException.  Therefore it is probably a little more
                //severe or unexpected.  So, wrap in an AuthenticationException, log to warn, and propagate:
                String msg = "Authentication failed for token submission [" + token + "].  Possible unexpected " +
                        "error? (Typical or expected login exceptions should extend from AuthenticationException).";
                ae = new AuthenticationException(msg, t);
            }
            try {
                notifyFailure(token, ae);
            } catch (Throwable t2) {
                if (log.isWarnEnabled()) {
                    String msg = "Unable to send notification for failed authentication attempt - listener error?.  " +
                            "Please check your AuthenticationListener implementation(s).  Logging sending exception " +
                            "and propagating original AuthenticationException instead...";
                    log.warn(msg, t2);
                }
            }


            throw ae;
        }

        log.debug("Authentication successful for token [{}].  Returned account [{}]", token, info);

        notifySuccess(token, info);

        return info;
    }

© 著作权归作者所有

粉丝 0
博文 7
码字总数 14453
作品 0
贵阳
私信 提问
Shiro用starter方式优雅整合到SpringBoot中

Shiro用starter方式优雅整合到SpringBoot中 网上找到大部分文章都是以前SpringMVC下的整合方式,很多人都不知道shiro提供了官方的starter可以方便地跟SpringBoot整合。本文介绍我的3种整合思...

煲煲菜
09/29
0
0
非常详尽的 Shiro 架构解析!

Shiro是什么? Apache Shiro是一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权,企业会话管理和加密。 Apache Shiro的首要目标是易于使用和理解。安全有时候是很复杂的,甚至...

技术小能手
2018/11/13
0
0
Apache Shiro 使用手册(一)Shiro架构介绍

一、什么是Shiro Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能: 认证 - 用户身份识别,常被称为用户“登录”; 授权 - 访问控制; 密码加密 - 保护或...

qq58edeba279279
2018/06/26
0
0
SpringBoot:集成Shiro之简述篇

前言 想要在SpringBoot 2.0 快速集成Shiro框架,简述篇可以省略跳过. 虽然近来一直在做游戏,但是涉及到后台问题,而且我不在想用SpringMVC框架来架构本次游戏过程,所以就使用SpringBoot 2.0来架...

神经骚栋
2018/09/03
0
0
在 Web 项目中应用 Apache Shiro 开源权限框架

Apache Shiro 是功能强大并且容易集成的开源权限框架,它能够完成认证、授权、加密、会话管理等功能。认证和授权为权限控制的核心,简单来说,“认证”就是证明你是谁? Web 应用程序一般做法...

IBMdW
2013/02/05
21.2K
38

没有更多内容

加载失败,请刷新页面

加载更多

centos7 安装 mysql5.7 版本(全)

centos 安装 版本说明 :centos7,mysql5.7 ,不是 centos7 可能有些命令不兼容 安装 mysql-server # 下载并安装 mysql yum wget -i -c http://dev.mysql.com/get/mysql57-community-relea......

sanri1993
29分钟前
4
0
Spring3.x升级到Spring4.x-5.x时关于MappingJacksonHttpMessageConverter的报错问题

在Spring4.x或者以上的版本强使用(不然会报错): org.springframework.http.converter.json.MappingJackson2HttpMessageConverter 如果是Spring4.0获者以下的版本可以使用MappingJacksonH...

code-ortaerc
32分钟前
3
0
OSG 渲染状态污染到其它节点怎么解决?

在根节点补上初始状态

洛克人杰洛
34分钟前
3
0
grid 布局 设置行列间距

本文转载于:专业的前端网站➪grid 布局 设置行列间距 <!DOCTYPE html><html lang="zh"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="widt......

前端老手
46分钟前
3
0
spring-data-elasticsearch 和 Jackson 配合使用的bug

下面先简单描述项目。 项目依赖: dependencies { implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-elasticsearch', version: '2.1.0.RELEASE'......

Landas
47分钟前
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部