Merge pull request #226 from bmhm/SHIRO-750

[SHIRO-750] update jax-rs dependency to jakarta.
diff --git a/NOTICE b/NOTICE
index afda186..9d26a95 100644
--- a/NOTICE
+++ b/NOTICE
@@ -9,7 +9,7 @@
 available at http://www.javaspecialists.eu/archive/Issue015.html,
 with continued modifications.  
 
-Certain parts (StringUtils etc.) of the source code for this 
-product was copied for simplicity and to reduce dependencies 
-from the source code developed by the Spring Framework Project 
-(http://www.springframework.org).
+Certain parts (StringUtils, IpAddressMatcher, etc.) of the source
+code for this  product was copied for simplicity and to reduce
+dependencies  from the source code developed by the Spring Framework
+Project  (http://www.springframework.org).
diff --git a/lang/src/main/java/org/apache/shiro/codec/Base64.java b/lang/src/main/java/org/apache/shiro/codec/Base64.java
index da31059..b277b13 100644
--- a/lang/src/main/java/org/apache/shiro/codec/Base64.java
+++ b/lang/src/main/java/org/apache/shiro/codec/Base64.java
@@ -39,190 +39,6 @@
 public class Base64 {
 
     /**
-     * Chunk size per RFC 2045 section 6.8.
-     * <p/>
-     * The character limit does not count the trailing CRLF, but counts all other characters, including any
-     * equal signs.
-     *
-     * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>
-     */
-    static final int CHUNK_SIZE = 76;
-
-    /**
-     * Chunk separator per RFC 2045 section 2.1.
-     *
-     * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a>
-     */
-    static final byte[] CHUNK_SEPARATOR = "\r\n".getBytes();
-
-    /**
-     * The base length.
-     */
-    private static final int BASELENGTH = 255;
-
-    /**
-     * Lookup length.
-     */
-    private static final int LOOKUPLENGTH = 64;
-
-    /**
-     * Used to calculate the number of bits in a byte.
-     */
-    private static final int EIGHTBIT = 8;
-
-    /**
-     * Used when encoding something which has fewer than 24 bits.
-     */
-    private static final int SIXTEENBIT = 16;
-
-    /**
-     * Used to determine how many bits data contains.
-     */
-    private static final int TWENTYFOURBITGROUP = 24;
-
-    /**
-     * Used to get the number of Quadruples.
-     */
-    private static final int FOURBYTE = 4;
-
-    /**
-     * Used to test the sign of a byte.
-     */
-    private static final int SIGN = -128;
-
-    /**
-     * Byte used to pad output.
-     */
-    private static final byte PAD = (byte) '=';
-
-    /**
-     * Contains the Base64 values <code>0</code> through <code>63</code> accessed by using character encodings as
-     * indices.
-     * <p/>
-     * <p>For example, <code>base64Alphabet['+']</code> returns <code>62</code>.</p>
-     * <p/>
-     * <p>The value of undefined encodings is <code>-1</code>.</p>
-     */
-    private static final byte[] base64Alphabet = new byte[BASELENGTH];
-
-    /**
-     * <p>Contains the Base64 encodings <code>A</code> through <code>Z</code>, followed by <code>a</code> through
-     * <code>z</code>, followed by <code>0</code> through <code>9</code>, followed by <code>+</code>, and
-     * <code>/</code>.</p>
-     * <p/>
-     * <p>This array is accessed by using character values as indices.</p>
-     * <p/>
-     * <p>For example, <code>lookUpBase64Alphabet[62] </code> returns <code>'+'</code>.</p>
-     */
-    private static final byte[] lookUpBase64Alphabet = new byte[LOOKUPLENGTH];
-
-    // Populating the lookup and character arrays
-
-    static {
-        for (int i = 0; i < BASELENGTH; i++) {
-            base64Alphabet[i] = (byte) -1;
-        }
-        for (int i = 'Z'; i >= 'A'; i--) {
-            base64Alphabet[i] = (byte) (i - 'A');
-        }
-        for (int i = 'z'; i >= 'a'; i--) {
-            base64Alphabet[i] = (byte) (i - 'a' + 26);
-        }
-        for (int i = '9'; i >= '0'; i--) {
-            base64Alphabet[i] = (byte) (i - '0' + 52);
-        }
-
-        base64Alphabet['+'] = 62;
-        base64Alphabet['/'] = 63;
-
-        for (int i = 0; i <= 25; i++) {
-            lookUpBase64Alphabet[i] = (byte) ('A' + i);
-        }
-
-        for (int i = 26, j = 0; i <= 51; i++, j++) {
-            lookUpBase64Alphabet[i] = (byte) ('a' + j);
-        }
-
-        for (int i = 52, j = 0; i <= 61; i++, j++) {
-            lookUpBase64Alphabet[i] = (byte) ('0' + j);
-        }
-
-        lookUpBase64Alphabet[62] = (byte) '+';
-        lookUpBase64Alphabet[63] = (byte) '/';
-    }
-
-    /**
-     * Returns whether or not the <code>octet</code> is in the base 64 alphabet.
-     *
-     * @param octect The value to test
-     * @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise.
-     */
-    private static boolean isBase64(byte octect) {
-        if (octect == PAD) {
-            return true;
-        } else //noinspection RedundantIfStatement
-            if (octect < 0 || base64Alphabet[octect] == -1) {
-                return false;
-            } else {
-                return true;
-            }
-    }
-
-    /**
-     * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet.
-     *
-     * @param arrayOctect byte array to test
-     * @return <code>true</code> if all bytes are valid characters in the Base64 alphabet or if the byte array is
-     *         empty; false, otherwise
-     */
-    public static boolean isBase64(byte[] arrayOctect) {
-
-        arrayOctect = discardWhitespace(arrayOctect);
-
-        int length = arrayOctect.length;
-        if (length == 0) {
-            // shouldn't a 0 length array be valid base64 data?
-            // return false;
-            return true;
-        }
-        for (int i = 0; i < length; i++) {
-            if (!isBase64(arrayOctect[i])) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Discards any whitespace from a base-64 encoded block.
-     *
-     * @param data The base-64 encoded data to discard the whitespace from.
-     * @return The data, less whitespace (see RFC 2045).
-     */
-    static byte[] discardWhitespace(byte[] data) {
-        byte groomedData[] = new byte[data.length];
-        int bytesCopied = 0;
-
-        for (byte aByte : data) {
-            switch (aByte) {
-                case (byte) ' ':
-                case (byte) '\n':
-                case (byte) '\r':
-                case (byte) '\t':
-                    break;
-                default:
-                    groomedData[bytesCopied++] = aByte;
-            }
-        }
-
-        byte packedData[] = new byte[bytesCopied];
-
-        System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
-
-        return packedData;
-    }
-
-    /**
      * Base64 encodes the specified byte array and then encodes it as a String using Shiro's preferred character
      * encoding (UTF-8).
      *
@@ -235,151 +51,15 @@
     }
 
     /**
-     * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks
-     *
-     * @param binaryData binary data to encodeToChars
-     * @return Base64 characters chunked in 76 character blocks
-     */
-    public static byte[] encodeChunked(byte[] binaryData) {
-        return encode(binaryData, true);
-    }
-
-    /**
      * Encodes a byte[] containing binary data, into a byte[] containing characters in the Base64 alphabet.
      *
      * @param pArray a byte array containing binary data
      * @return A byte array containing only Base64 character data
      */
     public static byte[] encode(byte[] pArray) {
-        return encode(pArray, false);
+        return java.util.Base64.getEncoder().encode(pArray);
     }
 
-    /**
-     * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
-     *
-     * @param binaryData Array containing binary data to encodeToChars.
-     * @param isChunked  if <code>true</code> this encoder will chunk the base64 output into 76 character blocks
-     * @return Base64-encoded data.
-     * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
-     */
-    public static byte[] encode(byte[] binaryData, boolean isChunked) {
-        long binaryDataLength = binaryData.length;
-        long lengthDataBits = binaryDataLength * EIGHTBIT;
-        long fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
-        long tripletCount = lengthDataBits / TWENTYFOURBITGROUP;
-        long encodedDataLengthLong;
-        int chunckCount = 0;
-
-        if (fewerThan24bits != 0) {
-            // data not divisible by 24 bit
-            encodedDataLengthLong = (tripletCount + 1) * 4;
-        } else {
-            // 16 or 8 bit
-            encodedDataLengthLong = tripletCount * 4;
-        }
-
-        // If the output is to be "chunked" into 76 character sections,
-        // for compliance with RFC 2045 MIME, then it is important to
-        // allow for extra length to account for the separator(s)
-        if (isChunked) {
-
-            chunckCount = (CHUNK_SEPARATOR.length == 0 ? 0 : (int) Math
-                    .ceil((float) encodedDataLengthLong / CHUNK_SIZE));
-            encodedDataLengthLong += chunckCount * CHUNK_SEPARATOR.length;
-        }
-
-        if (encodedDataLengthLong > Integer.MAX_VALUE) {
-            throw new IllegalArgumentException(
-                    "Input array too big, output array would be bigger than Integer.MAX_VALUE=" + Integer.MAX_VALUE);
-        }
-        int encodedDataLength = (int) encodedDataLengthLong;
-        byte encodedData[] = new byte[encodedDataLength];
-
-        byte k, l, b1, b2, b3;
-
-        int encodedIndex = 0;
-        int dataIndex;
-        int i;
-        int nextSeparatorIndex = CHUNK_SIZE;
-        int chunksSoFar = 0;
-
-        // log.debug("number of triplets = " + numberTriplets);
-        for (i = 0; i < tripletCount; i++) {
-            dataIndex = i * 3;
-            b1 = binaryData[dataIndex];
-            b2 = binaryData[dataIndex + 1];
-            b3 = binaryData[dataIndex + 2];
-
-            // log.debug("b1= " + b1 +", b2= " + b2 + ", b3= " + b3);
-
-            l = (byte) (b2 & 0x0f);
-            k = (byte) (b1 & 0x03);
-
-            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
-            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
-            byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
-
-            encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
-            // log.debug( "val2 = " + val2 );
-            // log.debug( "k4 = " + (k<<4) );
-            // log.debug( "vak = " + (val2 | (k<<4)) );
-            encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)];
-            encodedData[encodedIndex + 2] = lookUpBase64Alphabet[(l << 2) | val3];
-            encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f];
-
-            encodedIndex += 4;
-
-            // If we are chunking, let's put a chunk separator down.
-            if (isChunked) {
-                // this assumes that CHUNK_SIZE % 4 == 0
-                if (encodedIndex == nextSeparatorIndex) {
-                    System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedIndex, CHUNK_SEPARATOR.length);
-                    chunksSoFar++;
-                    nextSeparatorIndex = (CHUNK_SIZE * (chunksSoFar + 1)) + (chunksSoFar * CHUNK_SEPARATOR.length);
-                    encodedIndex += CHUNK_SEPARATOR.length;
-                }
-            }
-        }
-
-        // form integral number of 6-bit groups
-        dataIndex = i * 3;
-
-        if (fewerThan24bits == EIGHTBIT) {
-            b1 = binaryData[dataIndex];
-            k = (byte) (b1 & 0x03);
-            // log.debug("b1=" + b1);
-            // log.debug("b1<<2 = " + (b1>>2) );
-            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
-            encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
-            encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4];
-            encodedData[encodedIndex + 2] = PAD;
-            encodedData[encodedIndex + 3] = PAD;
-        } else if (fewerThan24bits == SIXTEENBIT) {
-
-            b1 = binaryData[dataIndex];
-            b2 = binaryData[dataIndex + 1];
-            l = (byte) (b2 & 0x0f);
-            k = (byte) (b1 & 0x03);
-
-            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
-            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
-
-            encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
-            encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)];
-            encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2];
-            encodedData[encodedIndex + 3] = PAD;
-        }
-
-        if (isChunked) {
-            // we also add a separator to the end of the final chunk.
-            if (chunksSoFar < chunckCount) {
-                System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedDataLength - CHUNK_SEPARATOR.length,
-                        CHUNK_SEPARATOR.length);
-            }
-        }
-
-        return encodedData;
-    }
 
     /**
      * Converts the specified UTF-8 Base64 encoded String and decodes it to a resultant UTF-8 encoded string.
@@ -421,86 +101,7 @@
      * @return Array containing decoded data.
      */
     public static byte[] decode(byte[] base64Data) {
-        // RFC 2045 requires that we discard ALL non-Base64 characters
-        base64Data = discardNonBase64(base64Data);
-
-        // handle the edge case, so we don't have to worry about it later
-        if (base64Data.length == 0) {
-            return new byte[0];
-        }
-
-        int numberQuadruple = base64Data.length / FOURBYTE;
-        byte decodedData[];
-        byte b1, b2, b3, b4, marker0, marker1;
-
-        // Throw away anything not in base64Data
-
-        int encodedIndex = 0;
-        int dataIndex;
-        {
-            // this sizes the output array properly - rlw
-            int lastData = base64Data.length;
-            // ignore the '=' padding
-            while (base64Data[lastData - 1] == PAD) {
-                if (--lastData == 0) {
-                    return new byte[0];
-                }
-            }
-            decodedData = new byte[lastData - numberQuadruple];
-        }
-
-        for (int i = 0; i < numberQuadruple; i++) {
-            dataIndex = i * 4;
-            marker0 = base64Data[dataIndex + 2];
-            marker1 = base64Data[dataIndex + 3];
-
-            b1 = base64Alphabet[base64Data[dataIndex]];
-            b2 = base64Alphabet[base64Data[dataIndex + 1]];
-
-            if (marker0 != PAD && marker1 != PAD) {
-                // No PAD e.g 3cQl
-                b3 = base64Alphabet[marker0];
-                b4 = base64Alphabet[marker1];
-
-                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
-                decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
-                decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
-            } else if (marker0 == PAD) {
-                // Two PAD e.g. 3c[Pad][Pad]
-                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
-            } else {
-                // One PAD e.g. 3cQ[Pad]
-                b3 = base64Alphabet[marker0];
-                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
-                decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
-            }
-            encodedIndex += 3;
-        }
-        return decodedData;
-    }
-
-    /**
-     * Discards any characters outside of the base64 alphabet, per the requirements on page 25 of RFC 2045 - "Any
-     * characters outside of the base64 alphabet are to be ignored in base64 encoded data."
-     *
-     * @param data The base-64 encoded data to groom
-     * @return The data, less non-base64 characters (see RFC 2045).
-     */
-    static byte[] discardNonBase64(byte[] data) {
-        byte groomedData[] = new byte[data.length];
-        int bytesCopied = 0;
-
-        for (byte aByte : data) {
-            if (isBase64(aByte)) {
-                groomedData[bytesCopied++] = aByte;
-            }
-        }
-
-        byte packedData[] = new byte[bytesCopied];
-
-        System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
-
-        return packedData;
+        return java.util.Base64.getDecoder().decode(base64Data);
     }
 
 }
diff --git a/pom.xml b/pom.xml
index 0ecade0..e154ead 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,7 +78,6 @@
         <!-- Don't change this version without also changing the shiro-aspect and shiro-features
              modules' OSGi metadata: -->
         <aspectj.version>1.9.4</aspectj.version>
-        <cas.client.core.version>3.2.2</cas.client.core.version>
         <commons.beanutils.version>1.9.4</commons.beanutils.version>
         <commons.cli.version>1.4</commons.cli.version>
         <commons.codec.version>1.14</commons.codec.version>
@@ -750,11 +749,6 @@
             </dependency>
             <dependency>
                 <groupId>org.apache.shiro</groupId>
-                <artifactId>shiro-cas</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>org.apache.shiro</groupId>
                 <artifactId>shiro-ehcache</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -847,13 +841,6 @@
                 <version>${javax.annotation.api.version}</version>
             </dependency>
             <dependency>
-                <!-- runtime dependency for the shiro-cas module: -->
-                <groupId>commons-codec</groupId>
-                <artifactId>commons-codec</artifactId>
-                <version>${commons.codec.version}</version>
-                <scope>runtime</scope>
-            </dependency>
-            <dependency>
                 <groupId>org.aspectj</groupId>
                 <artifactId>aspectjrt</artifactId>
                 <version>${aspectj.version}</version>
diff --git a/src/owasp-suppression.xml b/src/owasp-suppression.xml
index 76e2b0b..03ae97d 100644
--- a/src/owasp-suppression.xml
+++ b/src/owasp-suppression.xml
@@ -31,10 +31,4 @@
         <cpe>cpe:/a:apache:tomcat:8.5.5</cpe>
     </suppress>
 
-    <suppress>
-        <notes><![CDATA[ file name: opensaml-1.1.jar ]]></notes>
-        <sha1>21ec22368b6baa211a29887e162aa4cf9a8f3c60</sha1>
-        <cpe>cpe:/a:internet2:opensaml:1.1</cpe>
-    </suppress>
-
-</suppressions>
\ No newline at end of file
+</suppressions>
diff --git a/support/cas/pom.xml b/support/cas/pom.xml
deleted file mode 100644
index c0909ae..0000000
--- a/support/cas/pom.xml
+++ /dev/null
@@ -1,89 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ 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
-  ~ "License"); 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
-  ~ "AS IS" 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.
-  -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
-
-    <parent>
-        <groupId>org.apache.shiro</groupId>
-        <artifactId>shiro-support</artifactId>
-        <version>2.0.0-SNAPSHOT</version>
-        <relativePath>../pom.xml</relativePath>
-    </parent>
-
-    <modelVersion>4.0.0</modelVersion>
-    <artifactId>shiro-cas</artifactId>
-    <name>Apache Shiro :: Support :: CAS</name>
-    <packaging>bundle</packaging>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.shiro</groupId>
-            <artifactId>shiro-web</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.jasig.cas.client</groupId>
-            <artifactId>cas-client-core</artifactId>
-            <version>${cas.client.core.version}</version>
-        </dependency>
-        <dependency>
-            <!-- for Optional SAML ticket validation: -->
-            <groupId>commons-codec</groupId>
-            <artifactId>commons-codec</artifactId>
-            <optional>true</optional>
-        </dependency>
-        <dependency>
-            <!-- for Optional SAML ticket validation: -->
-            <groupId>org.opensaml</groupId>
-            <artifactId>opensaml</artifactId>
-            <version>1.1</version>
-            <scope>runtime</scope>
-            <optional>true</optional>
-        </dependency>
-        <dependency>
-            <!-- for Optional SAML ticket validation: -->
-            <groupId>org.apache.santuario</groupId>
-            <artifactId>xmlsec</artifactId>
-            <version>2.1.4</version>
-            <scope>runtime</scope>
-            <optional>true</optional>
-        </dependency>
-    </dependencies>
-
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.felix</groupId>
-                <artifactId>maven-bundle-plugin</artifactId>
-                <extensions>true</extensions>
-                <configuration>
-                    <instructions>
-                        <Bundle-SymbolicName>org.apache.shiro.cas</Bundle-SymbolicName>
-                        <Export-Package>org.apache.shiro.cas*;version=${project.version}</Export-Package>
-                        <Import-Package>
-                            org.apache.shiro*;version="${shiro.osgi.importRange}",
-                            org.jasig.cas.client*;version="[3.2, 4)",
-                            *
-                        </Import-Package>
-                    </instructions>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-
-</project>
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java b/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java
deleted file mode 100644
index e3add40..0000000
--- a/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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
- * "License"); 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
- * "AS IS" 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.cas;
-
-import org.apache.shiro.authc.AuthenticationException;
-
-/**
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-public class CasAuthenticationException extends AuthenticationException {
-
-    public CasAuthenticationException() {
-        super();
-    }
-
-    public CasAuthenticationException(String message) {
-        super(message);
-    }
-
-    public CasAuthenticationException(Throwable cause) {
-        super(cause);
-    }
-
-    public CasAuthenticationException(String message, Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java b/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java
deleted file mode 100644
index 88262a8..0000000
--- a/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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
- * "License"); 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
- * "AS IS" 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.cas;
-
-import org.apache.shiro.authc.AuthenticationException;
-import org.apache.shiro.authc.AuthenticationToken;
-import org.apache.shiro.subject.Subject;
-import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
-import org.apache.shiro.web.util.WebUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import java.io.IOException;
-
-/**
- * This filter validates the CAS service ticket to authenticate the user.  It must be configured on the URL recognized
- * by the CAS server.  For example, in {@code shiro.ini}:
- * <pre>
- * [main]
- * casFilter = org.apache.shiro.cas.CasFilter
- * ...
- *
- * [urls]
- * /shiro-cas = casFilter
- * ...
- * </pre>
- * (example : http://host:port/mycontextpath/shiro-cas)
- *
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-public class CasFilter extends AuthenticatingFilter {
-    
-    private static Logger logger = LoggerFactory.getLogger(CasFilter.class);
-    
-    // the name of the parameter service ticket in url
-    private static final String TICKET_PARAMETER = "ticket";
-    
-    // the url where the application is redirected if the CAS service ticket validation failed (example : /mycontextpatch/cas_error.jsp)
-    private String failureUrl;
-    
-    /**
-     * The token created for this authentication is a CasToken containing the CAS service ticket received on the CAS service url (on which
-     * the filter must be configured).
-     * 
-     * @param request the incoming request
-     * @param response the outgoing response
-     * @throws Exception if there is an error processing the request.
-     */
-    @Override
-    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
-        HttpServletRequest httpRequest = (HttpServletRequest) request;
-        String ticket = httpRequest.getParameter(TICKET_PARAMETER);
-        return new CasToken(ticket);
-    }
-    
-    /**
-     * Execute login by creating {@link #createToken(javax.servlet.ServletRequest, javax.servlet.ServletResponse) token} and logging subject
-     * with this token.
-     * 
-     * @param request the incoming request
-     * @param response the outgoing response
-     * @throws Exception if there is an error processing the request.
-     */
-    @Override
-    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
-        return executeLogin(request, response);
-    }
-    
-    /**
-     * Returns <code>false</code> to always force authentication (user is never considered authenticated by this filter).
-     * 
-     * @param request the incoming request
-     * @param response the outgoing response
-     * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
-     * @return <code>false</code>
-     */
-    @Override
-    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
-        return false;
-    }
-    
-    /**
-     * If login has been successful, redirect user to the original protected url.
-     * 
-     * @param token the token representing the current authentication
-     * @param subject the current authenticated subjet
-     * @param request the incoming request
-     * @param response the outgoing response
-     * @throws Exception if there is an error processing the request.
-     */
-    @Override
-    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
-                                     ServletResponse response) throws Exception {
-        issueSuccessRedirect(request, response);
-        return false;
-    }
-    
-    /**
-     * If login has failed, redirect user to the CAS error page (no ticket or ticket validation failed) except if the user is already
-     * authenticated, in which case redirect to the default success url.
-     * 
-     * @param token the token representing the current authentication
-     * @param ae the current authentication exception
-     * @param request the incoming request
-     * @param response the outgoing response
-     */
-    @Override
-    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request,
-                                     ServletResponse response) {
-        if (logger.isDebugEnabled()) {
-            logger.debug( "Authentication exception", ae );
-        }
-        // is user authenticated or in remember me mode ?
-        Subject subject = getSubject(request, response);
-        if (subject.isAuthenticated() || subject.isRemembered()) {
-            try {
-                issueSuccessRedirect(request, response);
-            } catch (Exception e) {
-                logger.error("Cannot redirect to the default success url", e);
-            }
-        } else {
-            try {
-                WebUtils.issueRedirect(request, response, failureUrl);
-            } catch (IOException e) {
-                logger.error("Cannot redirect to failure url : {}", failureUrl, e);
-            }
-        }
-        return false;
-    }
-    
-    public void setFailureUrl(String failureUrl) {
-        this.failureUrl = failureUrl;
-    }
-}
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java b/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java
deleted file mode 100644
index 791674a..0000000
--- a/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * 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
- * "License"); 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
- * "AS IS" 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.cas;
-
-import org.apache.shiro.authc.AuthenticationException;
-import org.apache.shiro.authc.AuthenticationInfo;
-import org.apache.shiro.authc.AuthenticationToken;
-import org.apache.shiro.authc.SimpleAuthenticationInfo;
-import org.apache.shiro.authz.AuthorizationInfo;
-import org.apache.shiro.authz.SimpleAuthorizationInfo;
-import org.apache.shiro.realm.AuthorizingRealm;
-import org.apache.shiro.subject.PrincipalCollection;
-import org.apache.shiro.subject.SimplePrincipalCollection;
-import org.apache.shiro.util.CollectionUtils;
-import org.apache.shiro.util.StringUtils;
-import org.jasig.cas.client.authentication.AttributePrincipal;
-import org.jasig.cas.client.validation.*;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This realm implementation acts as a CAS client to a CAS server for authentication and basic authorization.
- * <p/>
- * This realm functions by inspecting a submitted {@link org.apache.shiro.cas.CasToken CasToken} (which essentially 
- * wraps a CAS service ticket) and validates it against the CAS server using a configured CAS
- * {@link org.jasig.cas.client.validation.TicketValidator TicketValidator}.
- * <p/>
- * The {@link #getValidationProtocol() validationProtocol} is {@code CAS} by default, which indicates that a
- * a {@link org.jasig.cas.client.validation.Cas20ServiceTicketValidator Cas20ServiceTicketValidator}
- * will be used for ticket validation.  You can alternatively set
- * or {@link org.jasig.cas.client.validation.Saml11TicketValidator Saml11TicketValidator} of CAS client. It is based on
- * {@link AuthorizingRealm AuthorizingRealm} for both authentication and authorization. User id and attributes are retrieved from the CAS
- * service ticket validation response during authentication phase. Roles and permissions are computed during authorization phase (according
- * to the attributes previously retrieved).
- *
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-public class CasRealm extends AuthorizingRealm {
-
-    // default name of the CAS attribute for remember me authentication (CAS 3.4.10+)
-    public static final String DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME = "longTermAuthenticationRequestTokenUsed";
-    public static final String DEFAULT_VALIDATION_PROTOCOL = "CAS";
-    
-    private static Logger log = LoggerFactory.getLogger(CasRealm.class);
-    
-    // this is the url of the CAS server (example : http://host:port/cas)
-    private String casServerUrlPrefix;
-    
-    // this is the CAS service url of the application (example : http://host:port/mycontextpath/shiro-cas)
-    private String casService;
-    
-    /* CAS protocol to use for ticket validation : CAS (default) or SAML :
-       - CAS protocol can be used with CAS server version < 3.1 : in this case, no user attributes can be retrieved from the CAS ticket validation response (except if there are some customizations on CAS server side)
-       - SAML protocol can be used with CAS server version >= 3.1 : in this case, user attributes can be extracted from the CAS ticket validation response
-    */
-    private String validationProtocol = DEFAULT_VALIDATION_PROTOCOL;
-    
-    // default name of the CAS attribute for remember me authentication (CAS 3.4.10+)
-    private String rememberMeAttributeName = DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME;
-    
-    // this class from the CAS client is used to validate a service ticket on CAS server
-    private TicketValidator ticketValidator;
-    
-    // default roles to applied to authenticated user
-    private String defaultRoles;
-    
-    // default permissions to applied to authenticated user
-    private String defaultPermissions;
-    
-    // names of attributes containing roles
-    private String roleAttributeNames;
-    
-    // names of attributes containing permissions
-    private String permissionAttributeNames;
-    
-    public CasRealm() {
-        setAuthenticationTokenClass(CasToken.class);
-    }
-
-    @Override
-    protected void onInit() {
-        super.onInit();
-        ensureTicketValidator();
-    }
-
-    protected TicketValidator ensureTicketValidator() {
-        if (this.ticketValidator == null) {
-            this.ticketValidator = createTicketValidator();
-        }
-        return this.ticketValidator;
-    }
-    
-    protected TicketValidator createTicketValidator() {
-        String urlPrefix = getCasServerUrlPrefix();
-        if ("saml".equalsIgnoreCase(getValidationProtocol())) {
-            return new Saml11TicketValidator(urlPrefix);
-        }
-        return new Cas20ServiceTicketValidator(urlPrefix);
-    }
-    
-    /**
-     * Authenticates a user and retrieves its information.
-     * 
-     * @param token the authentication token
-     * @throws AuthenticationException if there is an error during authentication.
-     */
-    @Override
-    @SuppressWarnings("unchecked")
-    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
-        CasToken casToken = (CasToken) token;
-        if (token == null) {
-            return null;
-        }
-        
-        String ticket = (String)casToken.getCredentials();
-        if (!StringUtils.hasText(ticket)) {
-            return null;
-        }
-        
-        TicketValidator ticketValidator = ensureTicketValidator();
-
-        try {
-            // contact CAS server to validate service ticket
-            Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
-            // get principal, user id and attributes
-            AttributePrincipal casPrincipal = casAssertion.getPrincipal();
-            String userId = casPrincipal.getName();
-            log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[]{
-                    ticket, getCasServerUrlPrefix(), userId
-            });
-
-            Map<String, Object> attributes = casPrincipal.getAttributes();
-            // refresh authentication token (user id + remember me)
-            casToken.setUserId(userId);
-            String rememberMeAttributeName = getRememberMeAttributeName();
-            String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);
-            boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
-            if (isRemembered) {
-                casToken.setRememberMe(true);
-            }
-            // create simple authentication info
-            List<Object> principals = CollectionUtils.asList(userId, attributes);
-            PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
-            return new SimpleAuthenticationInfo(principalCollection, ticket);
-        } catch (TicketValidationException e) { 
-            throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e);
-        }
-    }
-    
-    /**
-     * Retrieves the AuthorizationInfo for the given principals (the CAS previously authenticated user : id + attributes).
-     * 
-     * @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
-     * @return the AuthorizationInfo associated with this principals.
-     */
-    @Override
-    @SuppressWarnings("unchecked")
-    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
-        // retrieve user information
-        SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) principals;
-        List<Object> listPrincipals = principalCollection.asList();
-        Map<String, String> attributes = (Map<String, String>) listPrincipals.get(1);
-        // create simple authorization info
-        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
-        // add default roles
-        addRoles(simpleAuthorizationInfo, split(defaultRoles));
-        // add default permissions
-        addPermissions(simpleAuthorizationInfo, split(defaultPermissions));
-        // get roles from attributes
-        List<String> attributeNames = split(roleAttributeNames);
-        for (String attributeName : attributeNames) {
-            String value = attributes.get(attributeName);
-            addRoles(simpleAuthorizationInfo, split(value));
-        }
-        // get permissions from attributes
-        attributeNames = split(permissionAttributeNames);
-        for (String attributeName : attributeNames) {
-            String value = attributes.get(attributeName);
-            addPermissions(simpleAuthorizationInfo, split(value));
-        }
-        return simpleAuthorizationInfo;
-    }
-    
-    /**
-     * Split a string into a list of not empty and trimmed strings, delimiter is a comma.
-     * 
-     * @param s the input string
-     * @return the list of not empty and trimmed strings
-     */
-    private List<String> split(String s) {
-        List<String> list = new ArrayList<String>();
-        String[] elements = StringUtils.split(s, ',');
-        if (elements != null && elements.length > 0) {
-            for (String element : elements) {
-                if (StringUtils.hasText(element)) {
-                    list.add(element.trim());
-                }
-            }
-        }
-        return list;
-    }
-    
-    /**
-     * Add roles to the simple authorization info.
-     * 
-     * @param simpleAuthorizationInfo
-     * @param roles the list of roles to add
-     */
-    private void addRoles(SimpleAuthorizationInfo simpleAuthorizationInfo, List<String> roles) {
-        for (String role : roles) {
-            simpleAuthorizationInfo.addRole(role);
-        }
-    }
-    
-    /**
-     * Add permissions to the simple authorization info.
-     * 
-     * @param simpleAuthorizationInfo
-     * @param permissions the list of permissions to add
-     */
-    private void addPermissions(SimpleAuthorizationInfo simpleAuthorizationInfo, List<String> permissions) {
-        for (String permission : permissions) {
-            simpleAuthorizationInfo.addStringPermission(permission);
-        }
-    }
-
-    public String getCasServerUrlPrefix() {
-        return casServerUrlPrefix;
-    }
-
-    public void setCasServerUrlPrefix(String casServerUrlPrefix) {
-        this.casServerUrlPrefix = casServerUrlPrefix;
-    }
-
-    public String getCasService() {
-        return casService;
-    }
-
-    public void setCasService(String casService) {
-        this.casService = casService;
-    }
-
-    public String getValidationProtocol() {
-        return validationProtocol;
-    }
-
-    public void setValidationProtocol(String validationProtocol) {
-        this.validationProtocol = validationProtocol;
-    }
-
-    public String getRememberMeAttributeName() {
-        return rememberMeAttributeName;
-    }
-
-    public void setRememberMeAttributeName(String rememberMeAttributeName) {
-        this.rememberMeAttributeName = rememberMeAttributeName;
-    }
-
-    public String getDefaultRoles() {
-        return defaultRoles;
-    }
-
-    public void setDefaultRoles(String defaultRoles) {
-        this.defaultRoles = defaultRoles;
-    }
-
-    public String getDefaultPermissions() {
-        return defaultPermissions;
-    }
-
-    public void setDefaultPermissions(String defaultPermissions) {
-        this.defaultPermissions = defaultPermissions;
-    }
-
-    public String getRoleAttributeNames() {
-        return roleAttributeNames;
-    }
-
-    public void setRoleAttributeNames(String roleAttributeNames) {
-        this.roleAttributeNames = roleAttributeNames;
-    }
-
-    public String getPermissionAttributeNames() {
-        return permissionAttributeNames;
-    }
-
-    public void setPermissionAttributeNames(String permissionAttributeNames) {
-        this.permissionAttributeNames = permissionAttributeNames;
-    }
-}
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java b/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java
deleted file mode 100644
index 51a774e..0000000
--- a/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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
- * "License"); 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
- * "AS IS" 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.cas;
-
-import org.apache.shiro.authc.AuthenticationToken;
-import org.apache.shiro.subject.Subject;
-import org.apache.shiro.subject.SubjectContext;
-import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
-
-/**
- * {@link org.apache.shiro.mgt.SubjectFactory Subject} implementation to be used in CAS-enabled applications.
- *
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-public class CasSubjectFactory extends DefaultWebSubjectFactory {
-
-    @Override
-    public Subject createSubject(SubjectContext context) {
-
-        //the authenticated flag is only set by the SecurityManager after a successful authentication attempt.
-        boolean authenticated = context.isAuthenticated();
-
-        //although the SecurityManager 'sees' the submission as a successful authentication, in reality, the
-        //login might have been just a CAS rememberMe login.  If so, set the authenticated flag appropriately:
-        if (authenticated) {
-
-            AuthenticationToken token = context.getAuthenticationToken();
-
-            if (token != null && token instanceof CasToken) {
-                CasToken casToken = (CasToken) token;
-                // set the authenticated flag of the context to true only if the CAS subject is not in a remember me mode
-                if (casToken.isRememberMe()) {
-                    context.setAuthenticated(false);
-                }
-            }
-        }
-
-        return super.createSubject(context);
-    }
-}
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java b/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java
deleted file mode 100644
index 221d1cb..0000000
--- a/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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
- * "License"); 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
- * "AS IS" 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.cas;
-
-import org.apache.shiro.authc.RememberMeAuthenticationToken;
-
-/**
- * This class represents a token for a CAS authentication (service ticket + user id + remember me).
- *
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-public class CasToken implements RememberMeAuthenticationToken {
-    
-    private static final long serialVersionUID = 8587329689973009598L;
-    
-    // the service ticket returned by the CAS server
-    private String ticket = null;
-    
-    // the user identifier
-    private String userId = null;
-    
-    // is the user in a remember me mode ?
-    private boolean isRememberMe = false;
-    
-    public CasToken(String ticket) {
-        this.ticket = ticket;
-    }
-    
-    public Object getPrincipal() {
-        return userId;
-    }
-    
-    public Object getCredentials() {
-        return ticket;
-    }
-    
-    public void setUserId(String userId) {
-        this.userId = userId;
-    }
-    
-    public boolean isRememberMe() {
-        return isRememberMe;
-    }
-    
-    public void setRememberMe(boolean isRememberMe) {
-        this.isRememberMe = isRememberMe;
-    }
-}
diff --git a/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy b/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy
deleted file mode 100644
index b917638..0000000
--- a/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * 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
- * "License"); 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
- * "AS IS" 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.cas
-
-import org.apache.shiro.authc.AuthenticationInfo
-import org.apache.shiro.authz.AuthorizationInfo
-
-import org.junit.Test
-
-import static org.junit.Assert.*
-
-/**
- * Unit tests for the {@link CasRealm} implementation.
- *
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-class CasRealmTest {
-
-    /**
-     * Creates a CAS realm with a ticket validator mock.
-     *
-     * @return CasRealm The CAS realm for testing.
-     */
-    private CasRealm createCasRealm() {
-        new CasRealm(ticketValidator: new MockServiceTicketValidator());
-    }
-
-    @Test
-    void testNoAttribute() {
-        CasRealm casRealm = createCasRealm();
-        CasToken casToken = new CasToken('$=defaultId');
-        AuthenticationInfo authenticationInfo = casRealm.doGetAuthenticationInfo(casToken);
-        def principals = authenticationInfo.principals
-        assertEquals "defaultId", principals.primaryPrincipal
-        def attributes = principals.asList()[1] //returns a map
-        assertEquals 0, attributes.size()
-        AuthorizationInfo authorizationInfo = casRealm.doGetAuthorizationInfo(principals);
-        assertNull authorizationInfo.stringPermissions
-        assertNull authorizationInfo.roles
-    }
-
-    @Test
-    void testNoAttributeDefaultRoleAndPermission() {
-        CasRealm casRealm = createCasRealm();
-        casRealm.defaultRoles = "defaultRole"
-        casRealm.defaultPermissions = "defaultPermission"
-        CasToken casToken = new CasToken('$=defaultId');
-        AuthenticationInfo authenticationInfo = casRealm.doGetAuthenticationInfo(casToken);
-        def principals = authenticationInfo.principals
-        assertEquals "defaultId", principals.primaryPrincipal
-        def attributes = principals.oneByType(Map)
-        assertEquals 0, attributes.size()
-        AuthorizationInfo authorizationInfo = casRealm.doGetAuthorizationInfo(principals);
-        assertTrue authorizationInfo.roles.contains("defaultRole")
-        assertTrue authorizationInfo.stringPermissions.contains("defaultPermission")
-    }
-
-    @Test
-    void testNoAttributeDefaultRolesAndPermissions() {
-        CasRealm casRealm = createCasRealm();
-        casRealm.defaultRoles = "defaultRole1, defaultRole2"
-        casRealm.defaultPermissions = "defaultPermission1,defaultPermission2"
-        CasToken casToken = new CasToken('$=defaultId');
-        AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
-        def principals = authcInfo.principals
-        assertEquals "defaultId", principals.primaryPrincipal
-        def attributes = principals.oneByType(Map)
-        assertEquals 0, attributes.size()
-        AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals)
-        assertEquals 2, authzInfo.roles.size()
-        assertTrue authzInfo.roles.contains("defaultRole1")
-        assertTrue authzInfo.roles.contains("defaultRole2")
-        assertEquals 2, authzInfo.stringPermissions.size()
-        assertTrue authzInfo.stringPermissions.contains("defaultPermission1")
-        assertTrue authzInfo.stringPermissions.contains("defaultPermission2")
-    }
-
-    @Test
-    void testRoleAndPermission() {
-        CasRealm casRealm = createCasRealm();
-        casRealm.roleAttributeNames = "role"
-        casRealm.permissionAttributeNames = "permission"
-        CasToken casToken = new CasToken('$=defaultId|role=aRole|permission=aPermission');
-        AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
-        def principals = authcInfo.principals
-        assertEquals "defaultId", principals.primaryPrincipal
-        def attributes = principals.oneByType(Map)
-        assertEquals 2, attributes.size()
-        assertEquals "aRole", attributes['role']
-        assertEquals "aPermission", attributes['permission']
-        AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
-        assertTrue authzInfo.roles.contains("aRole")
-        assertTrue authzInfo.stringPermissions.contains("aPermission")
-    }
-
-    @Test
-    void testRolesAndPermissions() {
-        CasRealm casRealm = createCasRealm();
-        casRealm.setRoleAttributeNames("role1 , role2");
-        casRealm.setPermissionAttributeNames("permission1,permission2");
-        CasToken casToken = new CasToken(
-                '$=defaultId|role1=role11 , role12|role2=role21,role22|permission1=permission11, permission12|permission2=permission21 ,permission22');
-        AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
-        def principals = authcInfo.principals
-        assertEquals "defaultId", principals.primaryPrincipal
-        def attributes = principals.oneByType(Map)
-        assertEquals "role11 , role12", attributes['role1']
-        assertEquals "role21,role22", attributes['role2']
-        assertEquals "permission11, permission12", attributes['permission1']
-        assertEquals "permission21 ,permission22", attributes['permission2']
-        AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
-        assertEquals 4, authzInfo.roles.size()
-        assertTrue authzInfo.roles.contains("role11")
-        assertTrue authzInfo.roles.contains("role12")
-        assertTrue authzInfo.roles.contains("role21")
-        assertTrue authzInfo.roles.contains("role22")
-        assertTrue authzInfo.stringPermissions.contains("permission11")
-        assertTrue authzInfo.stringPermissions.contains("permission12")
-        assertTrue authzInfo.stringPermissions.contains("permission21")
-        assertTrue authzInfo.stringPermissions.contains("permission22")
-    }
-
-    @Test
-    void testNotRememberMe() {
-        CasRealm casRealm = createCasRealm();
-        CasToken casToken = new CasToken("\$=defaultId|$CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME=false");
-        AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
-        def principals = authcInfo.principals
-        assertEquals "defaultId", principals.primaryPrincipal
-        def attributes = principals.oneByType(Map)
-        assertEquals "false", attributes[CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME]
-        assertFalse casToken.rememberMe
-        AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
-        assertNull authzInfo.stringPermissions
-        assertNull authzInfo.roles
-    }
-
-    @Test
-    void testRememberMe() {
-        CasRealm casRealm = createCasRealm();
-        CasToken casToken = new CasToken("\$=defaultId|$CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME=true");
-        AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
-        def principals = authcInfo.principals
-        assertEquals "defaultId", principals.primaryPrincipal
-        def attributes = principals.oneByType(Map)
-        assertEquals "true", attributes[CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME]
-        assertTrue casToken.rememberMe
-        AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
-        assertNull authzInfo.stringPermissions
-        assertNull authzInfo.roles
-    }
-
-    @Test
-    void testRememberMeNewAttributeName() {
-        CasRealm casRealm = createCasRealm();
-        casRealm.rememberMeAttributeName = "rme"
-        CasToken casToken = new CasToken('$=defaultId|rme=true');
-        AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
-        def principals = authcInfo.principals
-        assertEquals "defaultId", principals.primaryPrincipal
-        def attributes = principals.oneByType(Map)
-        assertEquals "true", attributes[casRealm.rememberMeAttributeName]
-        assertTrue casToken.rememberMe
-        AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
-        assertNull authzInfo.stringPermissions
-        assertNull authzInfo.roles
-    }
-
-}
diff --git a/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy b/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy
deleted file mode 100644
index 471111e..0000000
--- a/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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
- * "License"); 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
- * "AS IS" 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.cas
-
-import org.junit.Test
-
-import static org.junit.Assert.*
-
-/**
- * Unit tests for the {@link CasToken} implementation.
- *
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-class CasTokenTest {
-
-    @Test
-    void testPrincipal() {
-        CasToken casToken = new CasToken("fakeTicket")
-        assertNull casToken.principal
-        casToken.userId = "myUserId"
-        assertEquals "myUserId", casToken.principal
-    }
-
-    @Test
-    void testCredentials() {
-        CasToken casToken = new CasToken("fakeTicket")
-        assertEquals "fakeTicket", casToken.credentials
-    }
-
-    @Test
-    void testRememberMe() {
-        CasToken casToken = new CasToken("fakeTicket")
-        assertFalse casToken.rememberMe
-        casToken.rememberMe = true
-        assertTrue casToken.rememberMe
-    }
-}
diff --git a/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy b/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy
deleted file mode 100644
index fc46ab2..0000000
--- a/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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
- * "License"); 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
- * "AS IS" 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.cas
-
-import org.apache.shiro.util.StringUtils
-import org.jasig.cas.client.authentication.AttributePrincipalImpl
-import org.jasig.cas.client.validation.Assertion
-import org.jasig.cas.client.validation.AssertionImpl
-import org.jasig.cas.client.validation.TicketValidationException
-import org.jasig.cas.client.validation.TicketValidator
-
-/**
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-class MockServiceTicketValidator implements TicketValidator {
-
-    /**
-     * Returns different assertions according to the ticket input. The format of the mock ticket must be :
-     * key1=value1,key2=value2,...,keyN=valueN. If keyX is $, valueX is considered to be the name of the principal, otherwise (keyX, valueX)
-     * is considered to be an attribute of the principal.
-     */
-    public Assertion validate(String ticket, String service) throws TicketValidationException {
-        String name = null;
-        def attributes = [:]
-        String[] elements = StringUtils.split(ticket, '|' as char);
-        int length = elements.length;
-        for (int i = 0; i < length; i++) {
-            String[] pair = StringUtils.split(elements[i], '=' as char);
-            String key = pair[0].trim();
-            String value = pair[1].trim();
-            if ('$'.equals(key)) {
-                name = value;
-            } else {
-                attributes.put(key, value);
-            }
-        }
-        AttributePrincipalImpl attributePrincipalImpl = new AttributePrincipalImpl(name, attributes);
-        return new AssertionImpl(attributePrincipalImpl, [:]);
-
-    }
-}
diff --git a/support/features/src/main/resources/features.xml b/support/features/src/main/resources/features.xml
index 0196f23..d51aaca 100644
--- a/support/features/src/main/resources/features.xml
+++ b/support/features/src/main/resources/features.xml
@@ -40,13 +40,6 @@
         <bundle>mvn:org.apache.shiro/shiro-aspectj/${project.version}</bundle>
     </feature>
 
-    <feature name="shiro-cas" version="${project.version}">
-        <feature prerequisite='true'>wrap</feature>
-        <feature version="${project.version}">shiro-web</feature>
-        <bundle dependency='true'>wrap:mvn:org.jasig.cas.client/cas-client-core/${cas.client.core.version}$Export-Package=org.jasig*;version=${cas.client.core.version}</bundle>
-        <bundle>mvn:org.apache.shiro/shiro-cas/${project.version}</bundle>
-    </feature>
-
     <feature name="shiro-ehcache" version="${project.version}">
         <feature version="${project.version}">shiro-core</feature>
         <bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.ehcache/${ehcache.bundle.version}</bundle>
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java b/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java
index df95665..0bda765 100644
--- a/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java
+++ b/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java
@@ -38,6 +38,7 @@
 import org.apache.shiro.web.filter.authc.LogoutFilter;

 import org.apache.shiro.web.filter.authc.UserFilter;

 import org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter;

+import org.apache.shiro.web.filter.authz.IpFilter;

 import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;

 import org.apache.shiro.web.filter.authz.PortFilter;

 import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;

@@ -86,6 +87,8 @@
     @SuppressWarnings({"UnusedDeclaration"})

     public static final Key<SslFilter> SSL = Key.get(SslFilter.class);

     @SuppressWarnings({"UnusedDeclaration"})

+    public static final Key<IpFilter> IP = Key.get(IpFilter.class);

+    @SuppressWarnings({"UnusedDeclaration"})

     public static final Key<UserFilter> USER = Key.get(UserFilter.class);

 

 

diff --git a/support/pom.xml b/support/pom.xml
index fdc223f..c05cb7d 100644
--- a/support/pom.xml
+++ b/support/pom.xml
@@ -38,7 +38,6 @@
         <module>quartz</module>
         <module>spring</module>
         <module>guice</module>
-        <module>cas</module>
         <module>spring-boot</module>
         <module>servlet-plugin</module>
         <module>jaxrs</module>
diff --git a/test-coverage/pom.xml b/test-coverage/pom.xml
index 1abe085..ba97d08 100644
--- a/test-coverage/pom.xml
+++ b/test-coverage/pom.xml
@@ -76,10 +76,6 @@
             <groupId>org.apache.shiro</groupId>
             <artifactId>shiro-aspectj</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.apache.shiro</groupId>
-            <artifactId>shiro-cas</artifactId>
-        </dependency>
 
         <dependency>
             <groupId>org.apache.shiro</groupId>
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authz/IpAddressMatcher.java b/web/src/main/java/org/apache/shiro/web/filter/authz/IpAddressMatcher.java
new file mode 100644
index 0000000..107c80e
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authz/IpAddressMatcher.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * 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 "AS IS" 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.filter.authz;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+/**
+ * Matches a request based on IP Address or subnet mask matching against the remote
+ * address.
+ * <p>
+ * Both IPv6 and IPv4 addresses are supported, but a matcher which is configured with an
+ * IPv4 address will never match a request which returns an IPv6 address, and vice-versa.
+ *
+ * @see <a href="https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/security/web/util/matcher/IpAddressMatcher.java">Original Spring Security version</a>
+ * @since 2.0 
+ */
+public final class IpAddressMatcher {
+    private final int nMaskBits;
+    private final InetAddress requiredAddress;
+   
+    /**
+     * Takes a specific IP address or a range specified using the IP/Netmask (e.g.
+     * 192.168.1.0/24 or 202.24.0.0/14).
+     *
+     * @param ipAddress the address or range of addresses from which the request must
+     * come.
+     */
+    public IpAddressMatcher(String ipAddress) {
+        int i = ipAddress.indexOf('/');
+        if (i > 0) {
+            nMaskBits = Integer.parseInt(ipAddress.substring(i + 1));
+            ipAddress = ipAddress.substring(0, i);
+        } else {
+            nMaskBits = -1;
+        }
+        requiredAddress = parseAddress(ipAddress);
+    }
+
+    public boolean matches(String address) {
+        InetAddress remoteAddress = parseAddress(address);
+
+        if (!requiredAddress.getClass().equals(remoteAddress.getClass())) {
+            return false;
+        }
+
+        if (nMaskBits < 0) {
+            return remoteAddress.equals(requiredAddress);
+        }
+
+        byte[] remAddr = remoteAddress.getAddress();
+        byte[] reqAddr = requiredAddress.getAddress();
+
+        int oddBits = nMaskBits % 8;
+        int nMaskBytes = nMaskBits / 8 + (oddBits == 0 ? 0 : 1);
+        byte[] mask = new byte[nMaskBytes];
+
+        Arrays.fill(mask, 0, oddBits == 0 ? mask.length : mask.length - 1, (byte) 0xFF);
+
+        if (oddBits != 0) {
+            int finalByte = (1 << oddBits) - 1;
+            finalByte <<= 8 - oddBits;
+            mask[mask.length - 1] = (byte) finalByte;
+        }
+
+        for (int i = 0; i < mask.length; i++) {
+            if ((remAddr[i] & mask[i]) != (reqAddr[i] & mask[i])) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private InetAddress parseAddress(String address) {
+        try {
+            return InetAddress.getByName(address);
+        }
+        catch (UnknownHostException e) {
+            throw new IllegalArgumentException("Failed to parse address" + address, e);
+        }
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authz/IpFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authz/IpFilter.java
new file mode 100644
index 0000000..c5bd4a9
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authz/IpFilter.java
@@ -0,0 +1,142 @@
+/*
+ * 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
+ * "License"); 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
+ * "AS IS" 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.filter.authz;
+
+import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.util.StringUtils;
+import org.apache.shiro.web.util.WebUtils;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Collection;
+
+/**
+ * A Filter that requires the request to be from within a specific set of IP
+ * address ranges and / or not from with a specific (denied) set.
+ * <p/>
+ * Example config:
+ * <pre>
+ * [main]
+ * localLan = org.apache.shiro.web.filter.authz.IpFilter
+ * localLan.authorizedIps = 192.168.10.0/24
+ * localLan.deniedIps = 192.168.10.10/32
+ * <p/>
+ * [urls]
+ * /some/path/** = localLan
+ * # override for just this path:
+ * /another/path/** = localLan
+ * </pre>
+ *
+ * @since 2.0 
+ */
+public class IpFilter extends AuthorizationFilter {
+
+    private static IpSource DEFAULT_IP_SOURCE = new IpSource() {
+            public Collection<String> getAuthorizedIps() {
+                return Collections.emptySet();
+            }
+            public Collection<String> getDeniedIps() {
+                return Collections.emptySet();
+            }
+        };
+    
+    private IpSource ipSource = DEFAULT_IP_SOURCE;
+
+    private List<IpAddressMatcher> authorizedIpMatchers = Collections.emptyList();
+    private List<IpAddressMatcher> deniedIpMatchers = Collections.emptyList();
+
+    /**
+     * Specifies a set of (comma, tab or space-separated) strings representing
+     * IP address representing IPv4 or IPv6 ranges / CIDRs from which access
+     * should be allowed (if the IP is not included in either the list of
+     * statically defined denied IPs or the dynamic list of IPs obtained from
+     * the IP source.
+     */
+    public void setAuthorizedIps(String authorizedIps) {
+        String[] ips = StringUtils.tokenizeToStringArray(authorizedIps, ", \t");
+        if (ips != null && ips.length > 0) {
+            authorizedIpMatchers = new ArrayList<IpAddressMatcher>();
+            for (String ip : ips) {
+                authorizedIpMatchers.add(new IpAddressMatcher(ip));
+            }
+        }
+    }
+
+    /**
+     * Specified a set of (comma, tab or space-separated) strings representing
+     * IP address representing IPv4 or IPv6 ranges / CIDRs from which access
+     * should be blocked.
+     */
+    public void setDeniedIps(String deniedIps) {
+        String[] ips = StringUtils.tokenizeToStringArray(deniedIps, ", \t");
+        if (ips != null && ips.length > 0) {
+            deniedIpMatchers = new ArrayList<IpAddressMatcher>();
+            for (String ip : ips) {
+                deniedIpMatchers.add(new IpAddressMatcher(ip));
+            }
+        }
+    }
+
+    public void setIpSource(IpSource source) {
+        this.ipSource = source;
+    }
+
+    /**
+     * Returns the remote host for a given HTTP request. By default uses the
+     * remote method ServletRequest.getRemoteAddr(). May be overriden by
+     * subclasses to obtain address information from specific headers (e.g. XFF
+     * or Forwarded) in situations with reverse proxies.
+     */
+    public String getHostFromRequest(ServletRequest request) {
+        return request.getRemoteAddr();
+    }
+
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
+        String remoteIp = getHostFromRequest(request);
+        for (IpAddressMatcher matcher : deniedIpMatchers) {
+            if (matcher.matches(remoteIp)) {
+                return false;
+            }
+        }
+        for (String ip : ipSource.getDeniedIps()) {
+            IpAddressMatcher matcher = new IpAddressMatcher(ip);
+            if (matcher.matches(remoteIp)) {
+                return false;
+            }
+        }
+        for (IpAddressMatcher matcher : authorizedIpMatchers) {
+            if (matcher.matches(remoteIp)) {
+                return true;
+            }
+        }
+        for (String ip : ipSource.getAuthorizedIps()) {
+            IpAddressMatcher matcher = new IpAddressMatcher(ip);
+            if (matcher.matches(remoteIp)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authz/IpSource.java b/web/src/main/java/org/apache/shiro/web/filter/authz/IpSource.java
new file mode 100644
index 0000000..7b2f626
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authz/IpSource.java
@@ -0,0 +1,43 @@
+/*
+ * 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
+ * "License"); 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
+ * "AS IS" 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.filter.authz;
+
+import java.util.Collection;
+
+/**
+ * Represents a source of information for IP restrictions (see IpFilter)
+ * @since 2.0 
+ */
+public interface IpSource {
+
+    /**
+     * Returns a set of strings representing IP address representing
+     * IPv4 or IPv6 ranges / CIDRs. e.g. 192.168.0.0/16 from which
+     * access should be allowed (if and only if the IP is not included
+     * in the list of denied IPs)
+     */
+    public Collection<String> getAuthorizedIps();
+
+    /**
+     * Returns a set of strings representing IP address representing
+     * IPv4 or IPv6 ranges / CIDRs. e.g. 192.168.0.0/16 from which
+     * access should be denied.
+     */
+    public Collection<String> getDeniedIps();
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java b/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java
index 036f62f..a023feb 100644
--- a/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java
+++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java
@@ -41,6 +41,7 @@
     authc(FormAuthenticationFilter.class),
     authcBasic(BasicHttpAuthenticationFilter.class),
     authcBearer(BearerHttpAuthenticationFilter.class),
+    ip(IpFilter.class),
     logout(LogoutFilter.class),
     noSessionCreation(NoSessionCreationFilter.class),
     perms(PermissionsAuthorizationFilter.class),
diff --git a/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java b/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java
index 0c777ac..798bf40 100644
--- a/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java
+++ b/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java
@@ -212,9 +212,21 @@
             if (log.isTraceEnabled()) {
                 log.trace("Acquired Base64 encoded identity [" + base64 + "]");
             }
-            byte[] decoded = Base64.decode(base64);
+            byte[] decoded;
+            try {
+                decoded = Base64.decode(base64);
+            } catch (RuntimeException rtEx) {
+                /*
+                 * https://issues.apache.org/jira/browse/SHIRO-766:
+                 * If the base64 string cannot be decoded, just assume there is no valid cookie value.
+                 * */
+                getCookie().removeFrom(request, response);
+                log.warn("Unable to decode existing base64 encoded entity: [" + base64 + "].", rtEx);
+                return null;
+            }
+
             if (log.isTraceEnabled()) {
-                log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes.");
+                log.trace("Base64 decoded byte array length: " + decoded.length + " bytes.");
             }
             return decoded;
         } else {
diff --git a/web/src/test/java/org/apache/shiro/web/filter/authz/IpAddressMatcherTests.java b/web/src/test/java/org/apache/shiro/web/filter/authz/IpAddressMatcherTests.java
new file mode 100644
index 0000000..ad87303
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/filter/authz/IpAddressMatcherTests.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * 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 "AS IS" 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.filter.authz;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import org.junit.Test;
+
+/**
+ * @since 2.0 
+ */
+public class IpAddressMatcherTests {
+    final IpAddressMatcher v6matcher = new IpAddressMatcher("fe80::21f:5bff:fe33:bd68");
+    final IpAddressMatcher v4matcher = new IpAddressMatcher("192.168.1.104");
+    final String ipv6Address = "fe80::21f:5bff:fe33:bd68";
+    final String ipv4Address = "192.168.1.104";
+
+    @Test
+    public void ipv6MatcherMatchesIpv6Address() {
+        assertTrue(v6matcher.matches(ipv6Address));
+    }
+    
+    @Test
+    public void ipv6MatcherDoesntMatchIpv4Address() {
+        assertFalse(v6matcher.matches(ipv4Address));
+    }
+    
+    @Test
+    public void ipv4MatcherMatchesIpv4Address() {
+        assertTrue(v4matcher.matches(ipv4Address));
+    }
+    
+    @Test
+    public void ipv4SubnetMatchesCorrectly() throws Exception {
+        IpAddressMatcher matcher = new IpAddressMatcher("192.168.1.0/24");
+        assertTrue(matcher.matches(ipv4Address));
+        matcher = new IpAddressMatcher("192.168.1.128/25");
+        assertFalse(matcher.matches(ipv4Address));
+        assertTrue(matcher.matches("192.168.1.159"));
+    }
+    
+    @Test
+    public void ipv6RangeMatches() throws Exception {
+        IpAddressMatcher matcher = new IpAddressMatcher("2001:DB8::/48");
+        assertTrue(matcher.matches("2001:DB8:0:0:0:0:0:0"));
+        assertTrue(matcher.matches("2001:DB8:0:0:0:0:0:1"));
+        assertTrue(matcher.matches("2001:DB8:0:FFFF:FFFF:FFFF:FFFF:FFFF"));
+        assertFalse(matcher.matches("2001:DB8:1:0:0:0:0:0"));
+    }
+    
+    // https://github.com/spring-projects/spring-security/issues/1970q
+    @Test
+    public void zeroMaskMatchesAnything() throws Exception {
+        IpAddressMatcher matcher = new IpAddressMatcher("0.0.0.0/0");
+        
+        assertTrue(matcher.matches("123.4.5.6"));
+        assertTrue(matcher.matches("192.168.0.159"));
+        
+        matcher = new IpAddressMatcher("192.168.0.159/0");
+        assertTrue(matcher.matches("123.4.5.6"));
+        assertTrue(matcher.matches("192.168.0.159"));
+    }
+}
diff --git a/web/src/test/java/org/apache/shiro/web/filter/authz/IpFilterTest.java b/web/src/test/java/org/apache/shiro/web/filter/authz/IpFilterTest.java
new file mode 100644
index 0000000..c5def24
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/filter/authz/IpFilterTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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
+ * "License"); 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
+ * "AS IS" 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.filter.authz;
+
+import org.junit.Test;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * Test cases for the {@link AuthorizationFilter} class.
+ * @since 2.0 
+ */
+public class IpFilterTest {
+
+    @Test
+    public void accessShouldBeDeniedByDefault() throws Exception {
+        IpFilter filter = new IpFilter();
+        HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+        expect(request.getRemoteAddr()).andReturn("192.168.42.42");
+        replay(request);
+        assertFalse(filter.isAccessAllowed(request, null, null));
+        verify(request);
+    }
+
+    @Test
+    public void accessShouldBeDeniedWhenNotInTheAllowedSet() throws Exception {
+        IpFilter filter = new IpFilter();
+        filter.setAuthorizedIps("192.168.33/24");
+        HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+        expect(request.getRemoteAddr()).andReturn("192.168.42.42");
+        replay(request);
+        assertFalse(filter.isAccessAllowed(request, null, null));
+        verify(request);
+    }
+
+    @Test
+    public void accessShouldBeGrantedToIpsInTheAllowedSet() throws Exception {
+        IpFilter filter = new IpFilter();
+        filter.setAuthorizedIps("192.168.32/24 192.168.33/24 192.168.34/24");
+        HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+        expect(request.getRemoteAddr()).andReturn("192.168.33.44");
+        replay(request);
+        assertFalse(filter.isAccessAllowed(request, null, null));
+        verify(request);
+    }
+
+    @Test
+    public void deniedTakesPrecedenceOverAllowed() throws Exception {
+        IpFilter filter = new IpFilter();
+        filter.setAuthorizedIps("192.168.0.0/16");
+        filter.setDeniedIps("192.168.33.0/24");
+        HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+        expect(request.getRemoteAddr()).andReturn("192.168.33.44");
+        replay(request);
+        assertFalse(filter.isAccessAllowed(request, null, null));
+        verify(request);
+    }
+
+    @Test
+    public void willBlockAndAllowBasedOnIpSource() throws Exception {
+        IpSource source = new IpSource() {
+                public Collection<String> getAuthorizedIps() {
+                    return Collections.singleton("192.168.0.0/16");
+                }
+                public Collection<String> getDeniedIps() {
+                    return Collections.singleton("192.168.33.0/24");
+                }
+            };
+        IpFilter filter = new IpFilter();
+        filter.setIpSource(source);
+        HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+        expect(request.getRemoteAddr()).andReturn("192.168.33.44");
+        replay(request);
+        assertFalse(filter.isAccessAllowed(request, null, null));
+        verify(request);
+    }
+}
diff --git a/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java b/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java
index c4d0963..3b0cd21 100644
--- a/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java
+++ b/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java
@@ -35,6 +35,8 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import java.util.UUID;
+
 import static org.easymock.EasyMock.*;
 import static org.junit.Assert.*;
 
@@ -211,7 +213,7 @@
         CookieRememberMeManager mgr = new CookieRememberMeManager();
         try {
             mgr.getRememberedPrincipals(context);
-        } catch (CryptoException expected) {
+        } catch (IllegalArgumentException expected) {
             return;
         }
         fail("CryptoException was expected to be thrown");
@@ -244,4 +246,30 @@
         verify(mockResponse);
         verify(cookie);
     }
+
+    @Test
+    public void shouldIgnoreInvalidCookieValues() {
+        // given
+        HttpServletRequest mockRequest = createMock(HttpServletRequest.class);
+        HttpServletResponse mockResponse = createMock(HttpServletResponse.class);
+        WebSubjectContext context = new DefaultWebSubjectContext();
+        context.setServletRequest(mockRequest);
+        context.setServletResponse(mockResponse);
+
+        CookieRememberMeManager mgr = new CookieRememberMeManager();
+        Cookie[] cookies = new Cookie[]{
+                new Cookie(CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME, UUID.randomUUID().toString() + "%%ldapRealm")
+        };
+
+        expect(mockRequest.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY)).andReturn(null);
+        expect(mockRequest.getContextPath()).andReturn(null);
+        expect(mockRequest.getCookies()).andReturn(cookies);
+        replay(mockRequest);
+
+        // when
+        final byte[] rememberedSerializedIdentity = mgr.getRememberedSerializedIdentity(context);
+
+        // then
+        assertNull("should ignore invalid cookie values", rememberedSerializedIdentity);
+    }
 }