blob: 33e551e017d979ae8c03035ff5edbfc598d1f197 [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../../jacoco-resources/report.gif" type="image/gif"/><title>CookieRememberMeManager.java</title><link rel="stylesheet" href="../../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../../index.html" class="el_report">Apache Shiro :: Test Coverage</a> &gt; <a href="../index.html" class="el_bundle">shiro-web</a> &gt; <a href="index.source.html" class="el_package">org.apache.shiro.web.mgt</a> &gt; <span class="el_source">CookieRememberMeManager.java</span></div><h1>CookieRememberMeManager.java</h1><pre class="source lang-java linenums">/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* &quot;License&quot;); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* &quot;AS IS&quot; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.shiro.web.mgt;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.AbstractRememberMeManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.subject.WebSubject;
import org.apache.shiro.web.subject.WebSubjectContext;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Remembers a Subject's identity by saving the Subject's {@link Subject#getPrincipals() principals} to a {@link Cookie}
* for later retrieval.
* &lt;p/&gt;
* Cookie attributes (path, domain, maxAge, etc) may be set on this class's default
* {@link #getCookie() cookie} attribute, which acts as a template to use to set all properties of outgoing cookies
* created by this implementation.
* &lt;p/&gt;
* The default cookie has the following attribute values set:
* &lt;table&gt;
* &lt;tr&gt;
* &lt;th&gt;Attribute Name&lt;/th&gt;
* &lt;th&gt;Value&lt;/th&gt;
* &lt;/tr&gt;
* &lt;tr&gt;&lt;td&gt;{@link Cookie#getName() name}&lt;/td&gt;
* &lt;td&gt;{@code rememberMe}&lt;/td&gt;
* &lt;/tr&gt;
* &lt;tr&gt;
* &lt;td&gt;{@link Cookie#getPath() path}&lt;/td&gt;
* &lt;td&gt;{@code /}&lt;/td&gt;
* &lt;/tr&gt;
* &lt;tr&gt;
* &lt;td&gt;{@link Cookie#getMaxAge() maxAge}&lt;/td&gt;
* &lt;td&gt;{@link Cookie#ONE_YEAR Cookie.ONE_YEAR}&lt;/td&gt;
* &lt;/tr&gt;
* &lt;/table&gt;
* &lt;p/&gt;
* Note that because this class subclasses the {@link AbstractRememberMeManager} which already provides serialization
* and encryption logic, this class utilizes both for added security before setting the cookie value.
*
* @since 1.0
*/
public class CookieRememberMeManager extends AbstractRememberMeManager {
//TODO - complete JavaDoc
<span class="fc" id="L75"> private static transient final Logger log = LoggerFactory.getLogger(CookieRememberMeManager.class);</span>
/**
* The default name of the underlying rememberMe cookie which is {@code rememberMe}.
*/
public static final String DEFAULT_REMEMBER_ME_COOKIE_NAME = &quot;rememberMe&quot;;
private Cookie cookie;
/**
* Constructs a new {@code CookieRememberMeManager} with a default {@code rememberMe} cookie template.
*/
<span class="fc" id="L87"> public CookieRememberMeManager() {</span>
<span class="fc" id="L88"> Cookie cookie = new SimpleCookie(DEFAULT_REMEMBER_ME_COOKIE_NAME);</span>
<span class="fc" id="L89"> cookie.setHttpOnly(true);</span>
//One year should be long enough - most sites won't object to requiring a user to log in if they haven't visited
//in a year:
<span class="fc" id="L92"> cookie.setMaxAge(Cookie.ONE_YEAR);</span>
<span class="fc" id="L93"> this.cookie = cookie;</span>
<span class="fc" id="L94"> }</span>
/**
* Returns the cookie 'template' that will be used to set all attributes of outgoing rememberMe cookies created by
* this {@code RememberMeManager}. Outgoing cookies will match this one except for the
* {@link Cookie#getValue() value} attribute, which is necessarily set dynamically at runtime.
* &lt;p/&gt;
* Please see the class-level JavaDoc for the default cookie's attribute values.
*
* @return the cookie 'template' that will be used to set all attributes of outgoing rememberMe cookies created by
* this {@code RememberMeManager}.
*/
public Cookie getCookie() {
<span class="fc" id="L107"> return cookie;</span>
}
/**
* Sets the cookie 'template' that will be used to set all attributes of outgoing rememberMe cookies created by
* this {@code RememberMeManager}. Outgoing cookies will match this one except for the
* {@link Cookie#getValue() value} attribute, which is necessarily set dynamically at runtime.
* &lt;p/&gt;
* Please see the class-level JavaDoc for the default cookie's attribute values.
*
* @param cookie the cookie 'template' that will be used to set all attributes of outgoing rememberMe cookies created
* by this {@code RememberMeManager}.
*/
@SuppressWarnings({&quot;UnusedDeclaration&quot;})
public void setCookie(Cookie cookie) {
<span class="fc" id="L122"> this.cookie = cookie;</span>
<span class="fc" id="L123"> }</span>
/**
* Base64-encodes the specified serialized byte array and sets that base64-encoded String as the cookie value.
* &lt;p/&gt;
* The {@code subject} instance is expected to be a {@link WebSubject} instance with an HTTP Request/Response pair
* so an HTTP cookie can be set on the outgoing response. If it is not a {@code WebSubject} or that
* {@code WebSubject} does not have an HTTP Request/Response pair, this implementation does nothing.
*
* @param subject the Subject for which the identity is being serialized.
* @param serialized the serialized bytes to be persisted.
*/
protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
<span class="pc bpc" id="L137" title="1 of 2 branches missed."> if (!WebUtils.isHttp(subject)) {</span>
<span class="nc bnc" id="L138" title="All 2 branches missed."> if (log.isDebugEnabled()) {</span>
<span class="nc" id="L139"> String msg = &quot;Subject argument is not an HTTP-aware instance. This is required to obtain a servlet &quot; +</span>
&quot;request and response in order to set the rememberMe cookie. Returning immediately and &quot; +
&quot;ignoring rememberMe operation.&quot;;
<span class="nc" id="L142"> log.debug(msg);</span>
}
<span class="nc" id="L144"> return;</span>
}
<span class="fc" id="L148"> HttpServletRequest request = WebUtils.getHttpRequest(subject);</span>
<span class="fc" id="L149"> HttpServletResponse response = WebUtils.getHttpResponse(subject);</span>
//base 64 encode it and store as a cookie:
<span class="fc" id="L152"> String base64 = Base64.encodeToString(serialized);</span>
<span class="fc" id="L154"> Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies</span>
<span class="fc" id="L155"> Cookie cookie = new SimpleCookie(template);</span>
<span class="fc" id="L156"> cookie.setValue(base64);</span>
<span class="fc" id="L157"> cookie.saveTo(request, response);</span>
<span class="fc" id="L158"> }</span>
private boolean isIdentityRemoved(WebSubjectContext subjectContext) {
<span class="fc" id="L161"> ServletRequest request = subjectContext.resolveServletRequest();</span>
<span class="pc bpc" id="L162" title="1 of 2 branches missed."> if (request != null) {</span>
<span class="fc" id="L163"> Boolean removed = (Boolean) request.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY);</span>
<span class="pc bpc" id="L164" title="3 of 4 branches missed."> return removed != null &amp;&amp; removed;</span>
}
<span class="nc" id="L166"> return false;</span>
}
/**
* Returns a previously serialized identity byte array or {@code null} if the byte array could not be acquired.
* This implementation retrieves an HTTP cookie, Base64-decodes the cookie value, and returns the resulting byte
* array.
* &lt;p/&gt;
* The {@code SubjectContext} instance is expected to be a {@link WebSubjectContext} instance with an HTTP
* Request/Response pair so an HTTP cookie can be retrieved from the incoming request. If it is not a
* {@code WebSubjectContext} or that {@code WebSubjectContext} does not have an HTTP Request/Response pair, this
* implementation returns {@code null}.
*
* @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
* is being used to construct a {@link Subject} instance. To be used to assist with data
* lookup.
* @return a previously serialized identity byte array or {@code null} if the byte array could not be acquired.
*/
protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {
<span class="fc bfc" id="L187" title="All 2 branches covered."> if (!WebUtils.isHttp(subjectContext)) {</span>
<span class="pc bpc" id="L188" title="1 of 2 branches missed."> if (log.isDebugEnabled()) {</span>
<span class="fc" id="L189"> String msg = &quot;SubjectContext argument is not an HTTP-aware instance. This is required to obtain a &quot; +</span>
&quot;servlet request and response in order to retrieve the rememberMe cookie. Returning &quot; +
&quot;immediately and ignoring rememberMe operation.&quot;;
<span class="fc" id="L192"> log.debug(msg);</span>
}
<span class="fc" id="L194"> return null;</span>
}
<span class="fc" id="L197"> WebSubjectContext wsc = (WebSubjectContext) subjectContext;</span>
<span class="pc bpc" id="L198" title="1 of 2 branches missed."> if (isIdentityRemoved(wsc)) {</span>
<span class="nc" id="L199"> return null;</span>
}
<span class="fc" id="L202"> HttpServletRequest request = WebUtils.getHttpRequest(wsc);</span>
<span class="fc" id="L203"> HttpServletResponse response = WebUtils.getHttpResponse(wsc);</span>
<span class="fc" id="L205"> String base64 = getCookie().readValue(request, response);</span>
// Browsers do not always remove cookies immediately (SHIRO-183)
// ignore cookies that are scheduled for removal
<span class="fc bfc" id="L208" title="All 2 branches covered."> if (Cookie.DELETED_COOKIE_VALUE.equals(base64)) return null;</span>
<span class="fc bfc" id="L210" title="All 2 branches covered."> if (base64 != null) {</span>
<span class="fc" id="L211"> base64 = ensurePadding(base64);</span>
<span class="pc bpc" id="L212" title="1 of 2 branches missed."> if (log.isTraceEnabled()) {</span>
<span class="fc" id="L213"> log.trace(&quot;Acquired Base64 encoded identity [&quot; + base64 + &quot;]&quot;);</span>
}
<span class="fc" id="L215"> byte[] decoded = Base64.decode(base64);</span>
<span class="pc bpc" id="L216" title="1 of 2 branches missed."> if (log.isTraceEnabled()) {</span>
<span class="pc bpc" id="L217" title="1 of 2 branches missed."> log.trace(&quot;Base64 decoded byte array length: &quot; + (decoded != null ? decoded.length : 0) + &quot; bytes.&quot;);</span>
}
<span class="fc" id="L219"> return decoded;</span>
} else {
//no cookie set - new site visitor?
<span class="fc" id="L222"> return null;</span>
}
}
/**
* Sometimes a user agent will send the rememberMe cookie value without padding,
* most likely because {@code =} is a separator in the cookie header.
* &lt;p/&gt;
* Contributed by Luis Arias. Thanks Luis!
*
* @param base64 the base64 encoded String that may need to be padded
* @return the base64 String padded if necessary.
*/
private String ensurePadding(String base64) {
<span class="fc" id="L236"> int length = base64.length();</span>
<span class="fc bfc" id="L237" title="All 2 branches covered."> if (length % 4 != 0) {</span>
<span class="fc" id="L238"> StringBuilder sb = new StringBuilder(base64);</span>
<span class="fc bfc" id="L239" title="All 2 branches covered."> for (int i = 0; i &lt; length % 4; ++i) {</span>
<span class="fc" id="L240"> sb.append('=');</span>
}
<span class="fc" id="L242"> base64 = sb.toString();</span>
}
<span class="fc" id="L244"> return base64;</span>
}
/**
* Removes the 'rememberMe' cookie from the associated {@link WebSubject}'s request/response pair.
* &lt;p/&gt;
* The {@code subject} instance is expected to be a {@link WebSubject} instance with an HTTP Request/Response pair.
* If it is not a {@code WebSubject} or that {@code WebSubject} does not have an HTTP Request/Response pair, this
* implementation does nothing.
*
* @param subject the subject instance for which identity data should be forgotten from the underlying persistence
*/
protected void forgetIdentity(Subject subject) {
<span class="fc bfc" id="L257" title="All 2 branches covered."> if (WebUtils.isHttp(subject)) {</span>
<span class="fc" id="L258"> HttpServletRequest request = WebUtils.getHttpRequest(subject);</span>
<span class="fc" id="L259"> HttpServletResponse response = WebUtils.getHttpResponse(subject);</span>
<span class="fc" id="L260"> forgetIdentity(request, response);</span>
}
<span class="fc" id="L262"> }</span>
/**
* Removes the 'rememberMe' cookie from the associated {@link WebSubjectContext}'s request/response pair.
* &lt;p/&gt;
* The {@code SubjectContext} instance is expected to be a {@link WebSubjectContext} instance with an HTTP
* Request/Response pair. If it is not a {@code WebSubjectContext} or that {@code WebSubjectContext} does not
* have an HTTP Request/Response pair, this implementation does nothing.
*
* @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation
*/
public void forgetIdentity(SubjectContext subjectContext) {
<span class="pc bpc" id="L274" title="1 of 2 branches missed."> if (WebUtils.isHttp(subjectContext)) {</span>
<span class="fc" id="L275"> HttpServletRequest request = WebUtils.getHttpRequest(subjectContext);</span>
<span class="fc" id="L276"> HttpServletResponse response = WebUtils.getHttpResponse(subjectContext);</span>
<span class="fc" id="L277"> forgetIdentity(request, response);</span>
}
<span class="fc" id="L279"> }</span>
/**
* Removes the rememberMe cookie from the given request/response pair.
*
* @param request the incoming HTTP servlet request
* @param response the outgoing HTTP servlet response
*/
private void forgetIdentity(HttpServletRequest request, HttpServletResponse response) {
<span class="fc" id="L288"> getCookie().removeFrom(request, response);</span>
<span class="fc" id="L289"> }</span>
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.3.201901230119</span></div></body></html>