精华内容
下载资源
问答
  • 2-MD5-加盐校验-自定义密码校验-rest密码校验-客户端登录

    前言,本代码接接着上一讲代码进行。

    1、服务端、sso-server中的准备

    2、sso-server中添加jdbc依赖

    <!--新增支持jdbc验证-->
    <dependency>
        <groupId>org.apereo.cas</groupId>
        <artifactId>cas-server-support-jdbc</artifactId>
        <version>${cas.version}</version>
    </dependency>
    
    
    <dependency>
        <groupId>org.apereo.cas</groupId>
        <artifactId>cas-server-support-jdbc-drivers</artifactId>
        <version>5.1.2</version>
    </dependency>
    
    
    <!-- mysql 数据库驱动. -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>6.0.6</version>
    </dependency>
    
    

    添加jdbc依赖后的sso-server的pom.xml如下

    <?xml version="1.0" encoding="UTF-8"?>
    <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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>com.hlj.cas</groupId>
            <artifactId>com-hlj-cas</artifactId>
            <version>1.0-SNAPSHOT</version>
            <relativePath>../pom.xml</relativePath>
        </parent>
    
        <artifactId>sso-server</artifactId>
        <packaging>war</packaging>
    
        <name>sso-server</name>
        <description>单点登录</description>
    
    
        <dependencies>
            <dependency>
                <groupId>org.apereo.cas</groupId>
                <artifactId>cas-server-webapp-tomcat</artifactId>
                <version>${cas.version}</version>
                <type>war</type>
                <scope>runtime</scope>
            </dependency>
    
            <!--新增支持jdbc验证-->
            <dependency>
                <groupId>org.apereo.cas</groupId>
                <artifactId>cas-server-support-jdbc</artifactId>
                <version>${cas.version}</version>
            </dependency>
    
    
            <dependency>
                <groupId>org.apereo.cas</groupId>
                <artifactId>cas-server-support-jdbc-drivers</artifactId>
                <version>5.1.2</version>
            </dependency>
    
    
            <!-- mysql 数据库驱动. -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>6.0.6</version>
            </dependency>
        </dependencies>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.apereo.cas</groupId>
                    <artifactId>cas-server-support-bom</artifactId>
                    <version>${cas.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>com.rimerosolutions.maven.plugins</groupId>
                    <artifactId>wrapper-maven-plugin</artifactId>
                    <version>0.0.5</version>
                    <configuration>
                        <verifyDownload>true</verifyDownload>
                        <checksumAlgorithm>MD5</checksumAlgorithm>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${springboot.version}</version>
                    <configuration>
                        <mainClass>org.springframework.boot.loader.WarLauncher</mainClass>
                        <addResources>true</addResources>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.1.0</version>
                    <configuration>
                        <warName>cas</warName>
                        <failOnMissingWebXml>false</failOnMissingWebXml>
                        <recompressZippedFiles>false</recompressZippedFiles>
                        <archive>
                            <compress>false</compress>
                            <manifestFile>${project.build.directory}/war/work/org.apereo.cas/cas-server-webapp-tomcat/META-INF/MANIFEST.MF</manifestFile>
                        </archive>
                        <overlays>
                            <overlay>
                                <groupId>org.apereo.cas</groupId>
                                <artifactId>cas-server-webapp-tomcat</artifactId>
                            </overlay>
                        </overlays>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.3</version>
                </plugin>
            </plugins>
            <finalName>cas</finalName>
        </build>
    </project>
    
    
    

    2、MD5加密认证

    1、创建MD5加密工具类

    package com.hlj.sso.server.Utils;
    
    import java.io.UnsupportedEncodingException;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    
    /**
     * 采用MD5加密解密
     * @author tfq
     * @datetime 2011-10-13
     */
    public class MD5Util {
    
        /***
         * MD5加码 生成32位md5码
         */
        public static String string2MD5(String inStr){
            MessageDigest md5 = null;
            try{
                md5 = MessageDigest.getInstance("MD5");
            }catch (Exception e){
                System.out.println(e.toString());
                e.printStackTrace();
                return "";
            }
            char[] charArray = inStr.toCharArray();
            byte[] byteArray = new byte[charArray.length];
    
            for (int i = 0; i < charArray.length; i++)
                byteArray[i] = (byte) charArray[i];
            byte[] md5Bytes = md5.digest(byteArray);
            StringBuffer hexValue = new StringBuffer();
            for (int i = 0; i < md5Bytes.length; i++){
                int val = ((int) md5Bytes[i]) & 0xff;
                if (val < 16)
                    hexValue.append("0");
                hexValue.append(Integer.toHexString(val));
            }
            return hexValue.toString();
    
        }
    
        /**
         * 加密解密算法 执行一次加密,两次解密
         */
        public static String convertMD5(String inStr){
    
            char[] a = inStr.toCharArray();
            for (int i = 0; i < a.length; i++){
                a[i] = (char) (a[i] ^ 't');
            }
            String s = new String(a);
            return s;
    
        }
    
        // 测试主函数
        public static void main(String args[]) {
            String s = new String("HealerJean123456
    ");
            System.out.println("原始:" + s);
            System.out.println("MD5后:" + string2MD5(s));
            System.out.println("解密的:" + convertMD5(convertMD5(s)));
    
        }
    }
    
    

    [外链图片转存失败(img-snFyPC2p-1566552889524)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-112447@2x.png)]

    2、创建数据库表和添加测试数据

    字段名 字段类型 备注
    username varchar 登录账号
    password varchar 密码
    expired int 过期字段,1为过期,需修改密码
    disable int 不可用字段,1为不可用,禁用
    email varchar 邮箱,可不需要
    /*
    账号表
    */
    
    CREATE TABLE SYS_USER (
      USERNAME VARCHAR(30) PRIMARY KEY,
      PASSWORD VARCHAR(64) NOT NULL,
      EMAIL    VARCHAR(50),
      ADDRESS  VARCHAR(100),
      AGE      INT,
      EXPIRED INT,
      DISABLE INT
    );
    
    /*HealerJean123456*/
    INSERT INTO SYS_USER VALUES ('HealerJean', '40e5fd27a2f9db33d397d11617c2098b', 'mxzdhealer@gmail.com', '山西忻州', 24, 0, 0);
    /*123*/
    INSERT INTO SYS_USER VALUES ('admin', '202cb962ac59075b964b07152d234b70', 'huang.wenbin@foxmail.com', '广州天河', 24, 0, 0);
    /*12345678*/
    INSERT INTO SYS_USER VALUES ('zhangsan', '25d55ad283aa400af464c76d713c07ad', 'zhangsan@foxmail.com', '广州越秀', 26, 0, 0);
    /*1234*/
    /*锁定用户*/
    INSERT INTO SYS_USER VALUES('zhaosi','81dc9bdb52d04dc20036dbd8313ed055', 'zhaosi@foxmail.com', '广州海珠', 25, 0 , 1);
    /*12345*/
    /*不可用*/
    INSERT INTO SYS_USER VALUES('wangwu','827ccb0eea8a706c4c34a16891f84e7b', 'wangwu@foxmail.com', '广州番禺', 27, 1 , 0);
    
    

    3、在sso-server中添加配置,进行密码的验证

    # 1、MD5 密码直接校验
    #Query Database Authentication 数据库查询校验用户名开始
    #查询账号密码sql,必须包含密码字段,
    # 这里可以使用用户名或者邮箱来进行验证,根据数据字段如果是用户名则为username 邮箱则为email
    cas.authn.jdbc.query[0].sql=select * from sys_user where email=?
    #指定上面的sql查询字段名(必须)
    cas.authn.jdbc.query[0].fieldPassword=password
    #指定过期字段,1为过期,若过期不可用
    cas.authn.jdbc.query[0].fieldExpired=expired
    #为不可用字段段,1为不可用,需要修改密码
    cas.authn.jdbc.query[0].fieldDisabled=disabled
    #数据库方言hibernate的知识
    cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQL5Dialect
    #数据库驱动
    cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver
    #数据库连接
    cas.authn.jdbc.query[0].url=jdbc:mysql://localhost:3306/casnew?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=true
    #数据库用户名
    cas.authn.jdbc.query[0].user=root
    #数据库密码
    cas.authn.jdbc.query[0].password=123456
    #默认加密策略,通过encodingAlgorithm来指定算法,默认NONE不加密,可以自定义密码认证,比如ddkj就是写了一个自定义的认证
    cas.authn.jdbc.query[0].passwordEncoder.type=DEFAULT
    cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8
    cas.authn.jdbc.query[0].passwordEncoder.encodingAlgorithm=MD5
    #Query Database Authentication 数据库查询校验用户名结束
    
    

    4、开始MD5加密测试吧,朋友们,MD5加密测试吧,朋友们

    sudo ./build.sh

    使用用户名和密码mxzdhealer@gmail.com/HealerJean123456

    [外链图片转存失败(img-QiRT5dK7-1566552889525)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-113737@2x.png)]

    [外链图片转存失败(img-5snNjCwW-1566552889525)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-113838@2x.png)]

    3、使用自定义的java类进行密码的验证

    1、自定义的密码校验类

    package com.hlj.sso.server.EncoderUtils;
    
    import com.hlj.sso.server.Utils.MD5Util;
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    /**
     * 自定义密码认证
     *
     * @author chuan.ma
     * @since 2017/6/22
     */
    public class CustomPasswordEncoder implements PasswordEncoder {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(CustomPasswordEncoder.class);
    
        //前台传来的明文密码会进入这里。然后取得加密密码
        @Override
        public String encode(CharSequence password) {
            if (password == null) {
                return null;
            } else {
                return this.encodePassword(password.toString());
            }
        }
        //密码校验,匹配
        @Override
        public boolean matches(CharSequence rawPassword, String encodedPassword) {
            String encodedRawPassword = StringUtils.isNotBlank(rawPassword)?this.encode(rawPassword.toString()):null;
            return StringUtils.equals(encodedRawPassword, encodedPassword);
        }
    
        // 这部分则为通过前台传来的明文密码根据自己的东西返回获取数据库中的加密密码的格式,
        // 用于matchs方法中进行比较
        private String encodePassword(String password) {
            return MD5Util.string2MD5(password);
        }
    }
    
    

    2、配置文件中进行配置这个密码校验类,而不是直接写死MD5,但是也有局限性,针对随机盐,我们从这个校验类中查不到盐的值。后面会讲

    
    # 2、使用自定义类进行密码校验
    #数据库连接
    cas.authn.jdbc.query[0].url=jdbc:mysql://localhost:3306/casnew?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=true
    #数据库用户名
    cas.authn.jdbc.query[0].user=root
    #数据库密码
    cas.authn.jdbc.query[0].password=123456
    ## 这里可以添加多个验证条件,比如 是不是可用,直接 and expired =1 表示过期了
    cas.authn.jdbc.query[0].sql=select password from sys_user where email=?
    cas.authn.jdbc.query[0].fieldPassword=password
    # 这里就是最重要的一个,用来验证密码的
    cas.authn.jdbc.query[0].passwordEncoder.type=com.hlj.sso.server.EncoderUtils.CustomPasswordEncoder
    cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQL5Dialect
    cas.authn.jdbc.query[0].ddlAuto=none
    cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver
    cas.authn.jdbc.query[0].leakThreshold=10
    cas.authn.jdbc.query[0].propagationBehaviorName=PROPAGATION_REQUIRED
    cas.authn.jdbc.query[0].batchSize=1
    cas.authn.jdbc.query[0].healthQuery=SELECT 1
    cas.authn.jdbc.query[0].failFast=true
    
    

    3、使用邮箱和密码登录验证成功

    3、使用固定盐登录(固定盐)

    固定盐:根据某一个值生成的确定的盐,比如这里就是根据邮箱生成的固定的盐。

    1、加入依赖

    <!--加了依赖即将支持三种校验方式,包括文件存储用户校验器、拒绝用户校验器、shiro校验器-->
    <dependency>
        <groupId>org.apereo.cas</groupId>
        <artifactId>cas-server-support-generic</artifactId>
        <version>${cas.version}</version>
    </dependency>
    
    

    2、固定盐生成类

    这个类我放到test包下,不知道为什么在main下包不能引入。如果有知道为什么的请帮忙留意下,留言求告知

    package com.hlj.sso.server;
    
    import org.apache.shiro.crypto.hash.ConfigurableHashService;
    import org.apache.shiro.crypto.hash.DefaultHashService;
    import org.apache.shiro.crypto.hash.HashRequest;
    import org.apache.shiro.util.ByteSource;
    import org.junit.Test;
    
    
    public class PasswordSaltTest {
        private String staticSalt = ".";
        private String algorithmName = "MD5";
        private String encodedPassword = "123";
        private String dynaSalt = "admin_en";
    
        @Test
        public void test() throws Exception {
            ConfigurableHashService hashService = new DefaultHashService();
            hashService.setPrivateSalt(ByteSource.Util.bytes(this.staticSalt));
            hashService.setHashAlgorithmName(this.algorithmName);
            hashService.setHashIterations(2);
            HashRequest request = new HashRequest.Builder()
                    .setSalt(dynaSalt)
                    .setSource(encodedPassword)
                    .build();
            String res =  hashService.computeHash(request).toHex();
            System.out.println(res);
        }
    }
    
    

    3、创建数据库表和添加测试数据

    /*
    账号加盐表
    */
    CREATE TABLE SYS_USER_ENCODE (
      USERNAME VARCHAR(30) PRIMARY KEY,
      PASSWORD VARCHAR(64) NOT NULL,
      EMAIL    VARCHAR(50),
      ADDRESS  VARCHAR(100),
      AGE      INT,
      EXPIRED INT,
      DISABLED INT
    );
    
    
    ---加盐数据
    /*123  可以采用PasswordSaltTest输出值*/
    INSERT INTO SYS_USER_ENCODE VALUES ('admin_en', 'bfb194d5bd84a5fc77c1d303aefd36c3', 'huang.wenbin@foxmail.com', '江门蓬江', 24, 0, 0);
    INSERT INTO SYS_USER_ENCODE VALUES ('zhangsan_en', '68ae075edf004353a0403ee681e45056',  'zhangsan@foxmail.com', '深圳宝安', 21, 0, 0);
    INSERT INTO SYS_USER_ENCODE VALUES ('zhaosi_en', 'd66108d0409f68af538301b637f13a18',  'zhaosi@foxmail.com', '清远清新', 20, 0, 1);
    INSERT INTO SYS_USER_ENCODE VALUES ('wangwu_en', '44b907d6fee23a552348eabf5fcf1ac7',  'wangwu@foxmail.com', '佛山顺德', 19, 1, 0);
    
    
    

    4、sso-server中添加配置,进行加盐的校验

    #4、加盐处理
    #Encode Database Authentication 开始
    #加密次数
    cas.authn.jdbc.encode[0].numberOfIterations=2
    #该列名的值可替代上面的值,但对密码加密时必须取该值进行处理
    cas.authn.jdbc.encode[0].numberOfIterationsFieldName=
    # 盐值固定列
    cas.authn.jdbc.encode[0].saltFieldName=username
    #静态盐值
    cas.authn.jdbc.encode[0].staticSalt=.
    cas.authn.jdbc.encode[0].sql=select * from sys_user_encode where username=?
    #对处理盐值后的算法
    cas.authn.jdbc.encode[0].algorithmName=MD5
    cas.authn.jdbc.encode[0].passwordFieldName=password
    cas.authn.jdbc.encode[0].expiredFieldName=expired
    cas.authn.jdbc.encode[0].disabledFieldName=disabled
    #数据库连接
    cas.authn.jdbc.encode[0].url=jdbc:mysql://localhost:3306/casnew?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=true
    cas.authn.jdbc.encode[0].dialect=org.hibernate.dialect.MySQL5Dialect
    cas.authn.jdbc.encode[0].driverClass=com.mysql.jdbc.Driver
    cas.authn.jdbc.encode[0].user=root
    cas.authn.jdbc.encode[0].password=123456
    
    

    4、开始加盐测试吧,朋友们,MD5加密测试吧,朋友们

    这次我上面使用的是用户名加密,所以用户名和密码username和password

    4、rest密码校验,随机盐校验

    这个是可以使用随机盐的了,因为rest测试需要重新开启一个端口。也就是相当于重新开启一个项目。这里我新建了一个工程,sso-server-rest。端口为8888

    1、通过总司令新建Moudule ,新建springBoot工程。

    [外链图片转存失败(img-e2WMYgmp-1566552889526)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-130243@2x.png)]

    添加web和jpa 下面忘记写mysql了记得写上哦,依赖

    [外链图片转存失败(img-XZcdfb1Q-1566552889526)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-130417@2x.png)]

    3、总司令com-hlj-cas进行聚合,但并没有继承总司令。和ddkj有点像

    <modules>
        <module>sso-server</module>
        <module>sso-server-rest</module>
    </modules>
    
    

    4、介绍下rest的验证模式

    解释:cas明确规定如下:


    1、cas 服务端会通过post请求,并且把用户信息以”用户名:密码”进行Base64编码放在authorization请求头中


    2、返回200状态码并且格式为如下

    {
        "id": "mxzdhealer@gmail.com",
        "@class": "org.apereo.cas.authentication.principal.SimplePrincipal",
        "attributes": {
            "key": "keyVal"
        }
    }
    

    是成功的; 返回状态码403用户不可用;404账号不存在;423账户被锁定;428过期;其他登录失败

    5、sso-serveer中添加rest配置和测试生成随机盐的shiro 依赖

    1、依赖

    <!--restj认证-->
    <dependency>
        <groupId>org.apereo.cas</groupId>
        <artifactId>cas-server-support-rest-authentication</artifactId>
        <version>${cas.version}</version>
    </dependency>
    
    <!--仅用于产生随机盐-->
    <dependency>
    	<groupId>org.apache.shiro</groupId>
    	<artifactId>shiro-spring</artifactId>
    	<version>1.4.0</version>
    </dependency>
    
    

    2、配置

    
    #REST 认证开始
    cas.authn.rest.uri=http://localhost:8888/login
    #传输过程不适用md5加密,所以注释掉了,否则需要到rest中再进行解密,太麻烦了没有必要,默认为none
    #cas.authn.rest.passwordEncoder.type=DEFAULT
    #cas.authn.rest.passwordEncoder.characterEncoding=UTF-8
    #cas.authn.rest.passwordEncoder.encodingAlgorithm=MD5
    #REST 结束
    
    
    

    6、制作随机盐加密类

    package com.hlj.sso.server.Utils;
    
    import org.apache.shiro.crypto.SecureRandomNumberGenerator;
    import org.apache.shiro.crypto.hash.Md5Hash;
    
    public class RestSaltUtils {
    
    
        /**
         * 产生随机盐
         * @param args
         */
        public String salt(String password) {
            SecureRandomNumberGenerator secureRandomNumberGenerator = new SecureRandomNumberGenerator();
            String salt = secureRandomNumberGenerator.nextBytes().toHex();
            //对密码加密后,将加密后的密码和盐存入对象
            password = new Md5Hash(password, salt).toString();
            System.out.println(password);
    
            return password;
        }
    
        public static void main(String[] args) {
            String password ="HealerJean123456";
            RestSaltUtils restSaltUtils = new RestSaltUtils();
            restSaltUtils.salt(password);
        }
    
    }
    
    

    7、创建数据库表和添加数据

    
    /*
    账号表
    */
    
    
    CREATE TABLE `sys_user_rest_salt` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `address` varchar(255) COLLATE utf8_bin DEFAULT NULL,
      `age` int(11) NOT NULL,
      `disable` int(11) NOT NULL,
      `email` varchar(255) COLLATE utf8_bin DEFAULT NULL,
      `expired` int(11) NOT NULL,
      `password` varchar(255) COLLATE utf8_bin DEFAULT NULL,
      `salt` varchar(255) COLLATE utf8_bin DEFAULT NULL,
      `username` varchar(255) COLLATE utf8_bin DEFAULT NULL,
      `locked` int(11) NOT NULL,
      PRIMARY KEY (`id`)
    )
    
    
    INSERT INTO `sys_user_rest_salt` VALUES
    ('1', '山西忻州', '24', '0', 'mxzdhealer@gmail.com', '0', '971c4fef68430b0de3b91203cd27fd6a', 'aa6abe8f4c94beae41f94c62326500ca', 'HealerJean', '0'),
    ('2', '北京西城', '24', '0', 'huang.wenbin@gmail.com', '0', '971c4fef68430b0de3b91203cd27fd6a', 'aa6abe8f4c94beae41f94c62326500ca', 'admin2 ', '0'),
    /*锁定用户*/
    ('3', '江苏南京', '24', '1', 'zhangsan@gmail.com', '0', '971c4fef68430b0de3b91203cd27fd6a', 'aa6abe8f4c94beae41f94c62326500ca', 'zhangsan', '0'),
    /*不可用*/
    ('4', '浙江杭州', '24', '0', 'zhaosi@gmail.com', '1', '971c4fef68430b0de3b91203cd27fd6a', 'aa6abe8f4c94beae41f94c62326500ca', 'zhaosi', '0');
    
    
    

    8、sso-server-rest中添加代码

    1、创建实体类

    package com.hlj.sso.server.rest.bean;
    
    import javax.persistence.*;
    
    @Entity
    @Table(name = "SYS_USER_REST_SALT")
    public class SysUserRestSalt {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String username;
    
        private String salt;
    
        private String password;
    
        private  String email;
    
        private String address;
        private int age;
    
        //用户是否不可用,1不可用
        private int disable ;
    
        //用户是否过期,1。过期
        private int expired;
    
        private int locked;
    
        public SysUserRestSalt() {
        }
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
    	…………………………
    
    

    2、返回的实体data

    1、解释:@JsonProperty @JsonProperty不仅仅是在序列化的时候有用,反序列化的时候也有用,比如有些接口返回的是json字符串,命名又不是标准的驼峰形式,在映射成对象的时候,将类的属性上加上@JsonProperty注解,里面写上返回的json串对应的名字

    package com.hlj.sso.server.rest.BeanData;
    
    import com.fasterxml.jackson.annotation.JsonIgnore;
    import com.fasterxml.jackson.annotation.JsonProperty;
    
    import javax.validation.constraints.NotNull;
    import java.util.HashMap;
    import java.util.Map;
    
    public class SysUserRestSaltData {
    
      
        //@JsonProperty @JsonProperty不仅仅是在序列化的时候有用,
        // 反序列化的时候也有用,比如有些接口返回的是json字符串,
        // 命名又不是标准的驼峰形式,在映射成对象的时候,
        // 将类的属性上加上@JsonProperty注解,里面写上返回的json串对应的名字
        
        @JsonProperty("id")
        private  String email;
    
        @JsonProperty("@class")
        //需要返回实现org.apereo.cas.authentication.principal.Principal的类名接口
        private String clazz = "org.apereo.cas.authentication.principal.SimplePrincipal";
    
        @JsonProperty("attributes")
        private Map<String, Object> attributes = new HashMap<>();
    
        @JsonIgnore
        @NotNull
        private String username;
    
        @JsonIgnore
        @NotNull
        private String password;
    
        @JsonIgnore
        //用户是否不可用
        private boolean disable = false;
        @JsonIgnore
        //用户是否过期
        private boolean expired = false;
    
        @JsonIgnore
        //是否锁定
        private boolean locked = false;
    
        @JsonIgnore
        private Long id;
    
        @JsonIgnore
        private String salt;
    
    
        @JsonIgnore
        private String address;
    
        @JsonIgnore
        private int age;
    
    
    
        public boolean isLocked() {
            return locked;
        }
    
        public SysUserRestSaltData setLocked(int locked) {
            if(locked==1){
                this.locked = true;
            }
            return this;
        }
    
        public boolean isDisable() {
            return disable;
        }
    
        public SysUserRestSaltData setDisable(int disable) {
            if(disable==1){
                this.disable = true;
            }
            return this;
        }
    
        public boolean isExpired() {
            return expired;
        }
    
        public SysUserRestSaltData setExpired(int expired) {
            if(expired==1){
                this.expired = true;
            }        return this;
        }
    
        public String getPassword() {
            return password;
        }
    
        public SysUserRestSaltData setPassword(String password) {
            this.password = password;
            return this;
        }
    
        public String getUsername() {
            return username;
        }
    
        public SysUserRestSaltData setUsername(String username) {
            this.username = username;
            return this;
        }
    
        public String getClazz() {
            return clazz;
        }
    
        public Map<String, Object> getAttributes() {
            return attributes;
        }
    
        public SysUserRestSaltData setAttributes(Map<String, Object> attributes) {
            this.attributes = attributes;
            return this;
        }
    
    
        public Long getId() {
            return id;
        }
    
        public SysUserRestSaltData setId(Long id) {
            this.id = id;
            return this;
    
        }
    
        public String getSalt() {
            return salt;
        }
    
        public SysUserRestSaltData setSalt(String salt) {
            this.salt = salt;
            return this;
    
        }
    
        public String getEmail() {
            return email;
        }
    
        public SysUserRestSaltData setEmail(String email) {
            this.email = email;
            return this;
    
        }
    
        public String getAddress() {
            return address;
        }
    
        public SysUserRestSaltData setAddress(String address) {
            this.address = address;
            return this;
        }
    
        public int getAge() {
            return age;
        }
    
        public SysUserRestSaltData setAge(int age) {
            this.age = age;
            return this;
        }
    
    
        @JsonIgnore
        public SysUserRestSaltData addAttribute(String key, Object val) {
            getAttributes().put(key, val);
            return this;
        }
    
    }
    
    

    3、controller开始进行登录验证。

    这里就设计到了通过邮箱找到盐,再通过盐制作为密码,和数据库中进行比较。所以还需要pom中导入随机眼的shiro jar包

    <!--仅用于产生随机盐-->
    <dependency>
    	<groupId>org.apache.shiro</groupId>
    	<artifactId>shiro-spring</artifactId>
    	<version>1.4.0</version>
    </dependency>
    
    
    package  com.hlj.sso.server.rest.controller;
    
    import com.hlj.sso.server.rest.BeanData.SysUserRestSaltData;
    import com.hlj.sso.server.rest.service.SysUserRestSaltService;
    import org.apache.shiro.crypto.hash.Md5Hash;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.util.Base64Utils;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestHeader;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.io.UnsupportedEncodingException;
    
    /**
     * @Description 
     * @Author HealerJean
     * @Date   2018/3/11 下午1:15.
     */
    
    @RestController
    public class AuthUserController {
        private static final Logger LOGGER = LoggerFactory.getLogger(AuthUserController.class);
    
        @Autowired
        private SysUserRestSaltService sysUserRestSaltService;
    
    
    /**
          1. cas 服务端会通过post请求,并且把用户信息以"用户名:密码"进行Base64编码放在authorization请求头中
      2. 返回200状态码并且格式为
      {
    "@class":"org.apereo.cas.authentication.principal.SimplePrincipal",
      "id":"casuser",
       "attributes":{}
          } 
          是成功的
     2. 返回状态码
     . 403用户不可用;
     . 404账号不存在;
     . 423账户被锁定;
     . 428过期;其他登录失败
        
    
     */
        @PostMapping("/login")
        public Object login(@RequestHeader HttpHeaders httpHeaders) {
            LOGGER.info("Rest api login.");
            LOGGER.debug("request headers: {}", httpHeaders);
            SysUserRestSaltData user = null;
            try {
                //通过服务端传来的用户名和密码
                UserTemp userTemp = obtainUserFormHeader(httpHeaders);
    
                //尝试查找用户库是否存在
                 user= sysUserRestSaltService.findByEmail(userTemp.username);
    
    
                if (user.getId() != null) {
                    String password = new Md5Hash(userTemp.password, user.getSalt()).toString();
                    if (!user.getPassword().equals(password)) {
                        //密码不匹配
                        return new ResponseEntity(HttpStatus.BAD_REQUEST);
                    }
                    if (user.isDisable()) {
                        //禁用 403,表示以后也不能用了。其实它和锁定差不多
                        return new ResponseEntity(HttpStatus.FORBIDDEN);
                    }
                    if (user.isLocked()) {
                        //锁定 423
                        return new ResponseEntity(HttpStatus.LOCKED);
                    }
                    if (user.isExpired()) {
                        //过期 428
                        return new ResponseEntity(HttpStatus.PRECONDITION_REQUIRED);
                    }
                } else {
                    //不存在 404
                    return new ResponseEntity(HttpStatus.NOT_FOUND);
                }
            } catch (UnsupportedEncodingException e) {
                LOGGER.error("", e);
                new ResponseEntity(HttpStatus.BAD_REQUEST);
            }
            LOGGER.info("[{}] login is ok", user.getUsername());
            //成功返回json
            user.addAttribute("key", "keyVal");
            return user;
        }
    
        /**
         * 根据请求头获取用户名及密码
         *
         * @param httpHeaders
         * @return
         * @throws UnsupportedEncodingException
         */
        private UserTemp obtainUserFormHeader(HttpHeaders httpHeaders) throws UnsupportedEncodingException {
           
            //根据官方文档,当请求过来时,会通过把用户信息放在请求头authorization中,并且通过Basic认证方式加密
            String authorization = httpHeaders.getFirst("authorization");//将得到 Basic Base64(用户名:密码)
            String baseCredentials = authorization.split(" ")[1];
            String usernamePassword = new String(Base64Utils.decodeFromString(baseCredentials), "UTF-8");//用户名:密码
            LOGGER.debug("login user: {}", usernamePassword);
            String credentials[] = usernamePassword.split(":");
            return new UserTemp(credentials[0], credentials[1]);
        }
    
        /**
         * 解析请求过来的用户
         */
        private class UserTemp {
            private String username;
            private String password;
    
            public UserTemp(String username, String password) {
    
                this.username = username;
                this.password = password;
            }
    
        }
    
    }
    
    
    

    4、service中的验证

    这里需要注意的是,我省略了jpa和repository等的简单创建

    package com.hlj.sso.server.rest.service.impl;
    
    import com.hlj.sso.server.rest.BeanData.SysUserRestSaltData;
    import com.hlj.sso.server.rest.Repository.SysUserRestSaltRepository;
    import com.hlj.sso.server.rest.bean.SysUserRestSalt;
    import com.hlj.sso.server.rest.service.SysUserRestSaltService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class SysUserRestSaltServiceImpl implements SysUserRestSaltService{
    
        @Autowired
        private SysUserRestSaltRepository sysUserRestSaltRepository;
    
        @Override
        public SysUserRestSaltData findByEmail(String email) {
            SysUserRestSalt sysUserRestSalt =sysUserRestSaltRepository.findByEmail(email);
    
            SysUserRestSaltData sysUserRestSaltData = new SysUserRestSaltData();
           if(sysUserRestSalt!=null) {
               sysUserRestSaltData.setUsername(sysUserRestSalt.getUsername());
               sysUserRestSaltData.setEmail(sysUserRestSalt.getEmail());
               sysUserRestSaltData.setId(sysUserRestSalt.getId());
               sysUserRestSaltData.setLocked(sysUserRestSalt.getLocked());
               sysUserRestSaltData.setDisable(sysUserRestSalt.getDisable());
               sysUserRestSaltData.setExpired(sysUserRestSalt.getExpired());
               sysUserRestSaltData.setPassword(sysUserRestSalt.getPassword());
               sysUserRestSaltData.setSalt(sysUserRestSalt.getSalt());
               //下面这两个用到,可有可没有
               sysUserRestSaltData.setAddress(sysUserRestSalt.getAddress());
               sysUserRestSaltData.setAge(sysUserRestSalt.getAge());
    
           }
            return sysUserRestSaltData;
        }
    }
    
    

    9、使用postman进行测试

    [外链图片转存失败(img-RiKSqteT-1566552889527)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-165524@2x.png)]

    10、启动sso-server和sso-server-rest进行登录验证,使用email和密码登录

    [外链图片转存失败(img-hzapYVDR-1566552889527)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-170148@2x.png)]

    1、正确的账户 mxzdhealer@gmail.com HealerJean123456

    [外链图片转存失败(img-4pL6nlDK-1566552889528)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-170309@2x.png)]

    2、禁用的的账户 zhangsan@gmail.com HealerJean123456

    [外链图片转存失败(img-GZ9QtonX-1566552889528)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-170519@2x.png)]

    3、锁定的账户 huang.wenbin@gmail.com HealerJean123456
    [外链图片转存失败(img-Z7r0oAJL-1566552889528)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-192640@2x.png)]

    4、过期的账户,zhaosi@gmail.com ,zhaosi@gmail.com
    提示认证信息无效,和密码不正确是一个状态

    5.1、添加客户端获取并获取用户名

    客户端,端口为8081
    项目名称为 sso-client-one

    1、通过总司令新建Moudule ,新建springBoot工程,总司令要聚合它,但是它还不是总司令的儿子,ddkj项目

    [外链图片转存失败(img-OaznHcju-1566552889529)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-171426@2x.png)]

    添加依赖 web,jpa,mysql(jpahe mysql以后难免会遇到,所以提前加上了)

    [外链图片转存失败(img-cB8rNnW3-1566552889529)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-171720@2x.png)]

    2、总司令com-hlj-cas进行聚合,但并没有继承总司令。和ddkj有点像

    <modules>
        <module>sso-server</module>
        <module>sso-server-rest</module>
        <module>sso-client-one</module>
    </modules>
    
    

    3、添加cas客户端依赖

    <dependency>
    	<groupId>org.jasig.cas.client</groupId>
    	<artifactId>cas-client-core</artifactId>
    	<version>3.4.1</version>
    </dependency>
    
    <dependency>
    	<groupId>org.springframework.security</groupId>
    	<artifactId>spring-security-core</artifactId>
    	<version>4.2.3.RELEASE</version>
    </dependency>
    
    <dependency>
    	<groupId>org.springframework.security</groupId>
    	<artifactId>spring-security-web</artifactId>
    	<version>4.2.3.RELEASE</version>
    </dependency>
    

    4、配置host,添加域名

    因为有了客户端了,这个时候再用localhost就不好了,因为将来可能会有多个客户端,如果都用lcoahost 就会造成同域名相同session。这样就只能一个浏览器登录一个客户端了。和单点登录同时登录多个客户端想违背

    127.0.0.1 passport.sso.com
    127.0.0.1 casclientone
    127.0.0.1 casclienttwo

    5、配置客户端与 服务端直接的配置关系,sso-client-one的配置文件中

    
    #cas
    cas.server.url.prefix=http://passport.sso.com:8443/cas
    cas.server.url.login=${cas.server.url.prefix}/login
    cas.client.name=http://casclientone:${server.port}
    
    
    

    6、客户端配置登录 ticket,登出等过滤器

    package com.hlj.sso.client.one.config;
    
    import org.jasig.cas.client.authentication.AuthenticationFilter;
    import org.jasig.cas.client.session.SingleSignOutFilter;
    import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
    import org.jasig.cas.client.util.AssertionHolder;
    import org.jasig.cas.client.util.AssertionThreadLocalFilter;
    import org.jasig.cas.client.util.HttpServletRequestWrapperFilter;
    import org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.web.authentication.logout.LogoutFilter;
    import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
    
    /**
     * @Description 
     * @Author HealerJean
     * @Date   2018/3/11 下午5:37.
     */
    
    @Configuration
    public class CasConfig {
    
        @Value("${cas.server.url.login}")
        public String casServerLoginUrl;
    
        @Value("${cas.server.url.prefix}")
        public String casServerUrlPrefix;
    
        @Value("${cas.client.name}")
        public String casClientName;
    
        /**
         * 用于实现单点登出功能
         */
        @Bean
        public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> singleSignOutHttpSessionListener() {
            ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> listener = new ServletListenerRegistrationBean<>();
            listener.setEnabled(true);
            listener.setListener(new SingleSignOutHttpSessionListener());
            listener.setOrder(1);
            return listener;
        }
    
    
        /**
         * 该过滤器用于实现单点登出功能,单点退出配置,一定要放在其他filter之前
         */
        @Bean
        public FilterRegistrationBean logOutFilter() {
            FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
    
            LogoutFilter logoutFilter = new LogoutFilter(casServerUrlPrefix + "/logout?service=" + casClientName,new SecurityContextLogoutHandler());
            filterRegistration.setFilter(logoutFilter);
            filterRegistration.setEnabled(true);
            filterRegistration.addUrlPatterns("/logout");
            filterRegistration.addInitParameter("casServerUrlPrefix", casServerUrlPrefix);
            filterRegistration.addInitParameter("serverName", casClientName);
            filterRegistration.addInitParameter("redirectAfterValidation", "true");
            filterRegistration.setOrder(2);
            return filterRegistration;
        }
    
        /**
         * CAS Single Sign Out Filter
         * 该过滤器用于实现单点登出功能,单点退出配置,一定要放在其他filter之前
         */
        @Bean
        public FilterRegistrationBean singleSignOutFilter() {
            FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
            filterRegistration.setFilter(new SingleSignOutFilter());
            filterRegistration.addUrlPatterns("/*");
            filterRegistration.addInitParameter("casServerUrlPrefix", casServerUrlPrefix);
            filterRegistration.addInitParameter("serverName", casClientName);
            filterRegistration.setOrder(3);
            return filterRegistration;
        }
    
        /**
         * 该过滤器负责用户的认证工作
         */
        @Bean
        public FilterRegistrationBean authenticationFilter() {
            FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
            filterRegistration.setFilter(new AuthenticationFilter());
            filterRegistration.addUrlPatterns("/*");
            filterRegistration.addInitParameter("ignorePattern","/error|/public*|/assets*|/ftl*");
            filterRegistration.addInitParameter("casServerLoginUrl", casServerLoginUrl);
            filterRegistration.addInitParameter("encoding","UTF-8");
            filterRegistration.addInitParameter("serverName", casClientName);
            filterRegistration.addInitParameter("useSession", "true");
            filterRegistration.setOrder(4);
            return filterRegistration;
        }
    
    
        /**
         * 该过滤器负责对Ticket的校验工作
         */
        @Bean
        public FilterRegistrationBean cas20ProxyReceivingTicketValidationFilter() {
            FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
            Cas30ProxyReceivingTicketValidationFilter filter = new Cas30ProxyReceivingTicketValidationFilter();
            filterRegistration.setFilter(filter);
            filterRegistration.addUrlPatterns("/*");
            filterRegistration.addInitParameter("encoding","UTF-8");
            filterRegistration.addInitParameter("casServerUrlPrefix", casServerUrlPrefix);
            filterRegistration.addInitParameter("serverName", casClientName);
            filterRegistration.setOrder(5);
            return filterRegistration;
        }
    
        /**
         * 该过滤器对HttpServletRequest请求包装, 可通过HttpServletRequest的getRemoteUser()方法获得登录用户的登录名
         */
        @Bean
        public FilterRegistrationBean httpServletRequestWrapperFilter() {
            FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
            filterRegistration.setFilter(new HttpServletRequestWrapperFilter());
            filterRegistration.setEnabled(true);
            filterRegistration.addUrlPatterns("/*");
            filterRegistration.setOrder(6);
            return filterRegistration;
        }
    
        /**
         * 该过滤器使得可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。
         * 比如AssertionHolder.getAssertion().getPrincipal().getName()。
         * 这个类把Assertion信息放在ThreadLocal变量中,这样应用程序不在web层也能够获取到当前登录信息
         */
        @Bean
        public FilterRegistrationBean assertionThreadLocalFilter() {
            FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
            filterRegistration.setFilter(new AssertionThreadLocalFilter());
            filterRegistration.setEnabled(true);
            filterRegistration.addUrlPatterns("/*");
            filterRegistration.setOrder(7);
            return filterRegistration;
        }
    
    
        public static class RemoteUserUtil {
    
            public static Boolean hasLogin(){
                return AssertionHolder.getAssertion() != null;
            }
    
            /**
             * 获取单点登录用户id
             * @return
             */
            public static Long getRemoteUserId(){
                Object userId = AssertionHolder.getAssertion().getPrincipal().getAttributes().get("id");
                return userId == null ? null : Long.parseLong(userId.toString());
            }
    
            /**
             * 获取单点登录用户账户
             * @return
             */
            public static String getRemoteUserAccount(){
                return AssertionHolder.getAssertion().getPrincipal().getName();
            }
    
            /**
             * 获取单点登录用户名称
             * @return
             */
            public static String getRemoteUserName(){
                Object userName = AssertionHolder.getAssertion().getPrincipal().getAttributes().get("name");
                return userName == null ? null : userName.toString();
            }
    
            /**
             * 是否超级管理员
             * @return
             */
            public static boolean getRemoteUserSuper(){
                Object isSuper = AssertionHolder.getAssertion().getPrincipal().getAttributes().get("isSuper");
                return isSuper != null && isSuper.toString().equals("1");
            }
        }
    }
    
    
    

    7、添加controller,进行 浏览访问获取来自服务端的用户名

    package com.hlj.sso.client.one.controller;
    
    
    import org.jasig.cas.client.authentication.AttributePrincipal;
    import org.jasig.cas.client.util.AbstractCasFilter;
    import org.jasig.cas.client.util.AssertionHolder;
    import org.jasig.cas.client.validation.Assertion;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import javax.servlet.http.HttpServletRequest;
    
    @Controller
    public class HomeController {
    
        Logger logger = LoggerFactory.getLogger(HomeController.class);
        @GetMapping("hello")
        @ResponseBody
        public String home(HttpServletRequest request){
    
    
    
            String remoteUser =request.getRemoteUser();
            logger.info("1、request.getRemoteUser()"+remoteUser);
            
    
            //断言
            Assertion assertion = (Assertion) request.getSession().getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
            AttributePrincipal principal = assertion.getPrincipal();
            String username =  principal.getName();
            logger.info("2、AttributePrincipal.getName"+username);
    
    
            String user3 = AssertionHolder.getAssertion().getPrincipal().getName();
            logger.info("3、AssertionHolder.getAssertion().getPrincipal().getName()"+user3);
    
            return  null;
        }
    
    }
    
    
    

    8、朋友们真正牛逼的时候,到了,

    启动sso-server 8843,
    sso-server-rest 8888,
    sso-client-one 8081

    浏览器中输入 http://casclientone:8081/hello
    哈哈搞笑的事情出现了,他说没有经过服务端认证就访问了,原来是没有介入service

    [外链图片转存失败(img-HvoqTYNe-1566552889530)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-174623@2x.png)]

    1、接入service

    cas客户端接入称之为service,必须经过cas的允许才能进行登录

    当然不同的客户端可以做不同的事情,其中包括:
    • 自定义主题(各客户端登录页自定义)
    • 自定义属性(服务属性(固定)与用户属性(动态))
    • 自定义协议
    • 自定义登录后跳转方式,跳转路径
    • 授权策略(拒绝属性、可登录时间范围限制、等等)
    • 拒绝授权模式
    接下来来一个FAQ
    A: 什么是service,根cas有什么关系?
    B: service是使用型cas是服务型,cas好比游乐园,service好比来游乐园的游客

    A: 那service如何对接cas?service如何知道是否被允许接入?
    B: 好比游客需要进入游乐园,那么游客需要门票,获取门票有多种方式,可以用手机校验码,或者身份证进行获取。当然如果是犯罪分子门票都买不了,更何况进去游乐园

    A: 具体service如何作为客户端使用?
    B: 好比买门票,必须填写身份证号、手机号、付款等流程,当然也可以通过不同渠道购买,cas也有不同的客户端实现,cas client、pac4j等

    A: service接入有何好处?

    B: 被接入的service无需进行输入密码即可进入系统,好比A-service(OA系统)登录了,B-service(账单系统),C-service(CRM系统)无需再次登录,传统的方式有10个八个系统进行登录,用户会疯掉,开发员也需要管理多个系统的帐号数据
    说了一大推,不如来点正事,例如怎么配置,如何管理,马上马上~~

    1、sso-server 配置文件中添加
    
    # 5、 service 用于客户端启用,以及主题等
    #开启识别json文件,默认false
    cas.serviceRegistry.initFromJson=true
    #自动扫描服务配置,默认开启
    #cas.serviceRegistry.watcherEnabled=true
    #120秒扫描一遍
    #cas.serviceRegistry.repeatInterval=120000
    #延迟15秒开启
    #cas.serviceRegistry.startDelay=15000
    #默认json/yml资源加载路径为resources/services
    #cas.serviceRegistry.config.location=classpath:/services
    
    
    2、resource/service 新建json文件 Apereo-10000002.json

    [外链图片转存失败(img-Q1dHzidX-1566552889530)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-183000@2x.png)]

    {
      "@class" : "org.apereo.cas.services.RegexRegisteredService",
      "serviceId" : "^(https|imaps|http)://.*",
      "name" : "HTTPS and IMAPS",
      "id" : 10000001,
      "description" : "This service definition authorizes all application urls that support HTTPS and IMAPS and HTTP protocols.",
      "evaluationOrder" : 1,
      "attributeReleasePolicy": {
        "@class": "org.apereo.cas.services.ReturnAllAttributeReleasePolicy"
      }
    }
    
    

    4、重新启动吧,朋友们小插曲很重要哦

    浏览器中输入 http://casclientone:8081/hello
    自动到到了cas登录页面浏览器中变成了,下图所示

    [外链图片转存失败(img-YKqz7tB9-1566552889531)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-183738@2x.png)]

    登录成功 浏览器地址变成了 http://casclientone:8081/hello;jsessionid=4463DE12AB6D84330D7A847449D95BA1

    9、观察客户端控制台,取到了服务端过来的用户名

    QQ20180311-184456@2x

    5.2 客户端配置不需要浏览器访问的链接

    其实就是添加个过滤器路径url而已,让它不要被拦截

    1、添加contllor中url

    @GetMapping("url")
    @ResponseBody
    public String url(){
        return  "这个路径没有被拦截";
    }
    

    2、把 要拦截的url路径名字放入

    filterRegistration.addInitParameter("ignorePattern","/error|/public*|/assets*|/ftl*|url*");
    
    @Bean
    public FilterRegistrationBean authenticationFilter() {
        FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
        filterRegistration.setFilter(new AuthenticationFilter());
        filterRegistration.addUrlPatterns("/*");
        filterRegistration.addInitParameter("ignorePattern","/error|/public*|/assets*|/ftl*|url*");
        filterRegistration.addInitParameter("casServerLoginUrl", casServerLoginUrl);
        filterRegistration.addInitParameter("encoding","UTF-8");
        filterRegistration.addInitParameter("serverName", casClientName);
        filterRegistration.addInitParameter("useSession", "true");
        filterRegistration.setOrder(4);
    
    
        return filterRegistration;
    }
    
    

    3、启动吧,浏览器中直接访问

    http://casclientone:8081/url

    QQ20180311-190457@2x

    5.3、从服务的获取更加详细的信息,不是用了密码校验哦

    1、服务端配置查询的详细信息

    
    # 6、服务端返回客户端详细的用户信息
    cas.authn.attributeRepository.jdbc[0].singleRow=true
    cas.authn.attributeRepository.jdbc[0].order=0
    cas.authn.attributeRepository.jdbc[0].url=jdbc:mysql://localhost:3306/casnew?allowMultiQueries=true&zeroDateTimeBehavior=convertToNull
    cas.authn.attributeRepository.jdbc[0].user=root
    cas.authn.attributeRepository.jdbc[0].password=123456
    
    #username 返回登录的时候的提供的用户名,配置为和数据库一致,观察ddkj即可发现这里配置的为email,主要看登录的是什么
    cas.authn.attributeRepository.jdbc[0].username=email
    #使用邮箱登录的
    cas.authn.attributeRepository.jdbc[0].sql=select * from sys_user_rest_salt where email=?
    # 下面这里为配置要返回的信息,要返回的信息为email,address
    cas.authn.attributeRepository.jdbc[0].attributes.id=id
    cas.authn.attributeRepository.jdbc[0].attributes.email=email
    cas.authn.attributeRepository.jdbc[0].attributes.address=address
    cas.authn.attributeRepository.jdbc[0].dialect=org.hibernate.dialect.MySQL5Dialect
    cas.authn.attributeRepository.jdbc[0].ddlAuto=none
    cas.authn.attributeRepository.jdbc[0].driverClass=com.mysql.jdbc.Driver
    cas.authn.attributeRepository.jdbc[0].leakThreshold=10
    cas.authn.attributeRepository.jdbc[0].propagationBehaviorName=PROPAGATION_REQUIRED
    cas.authn.attributeRepository.jdbc[0].batchSize=1
    cas.authn.attributeRepository.jdbc[0].healthQuery=SELECT 1
    cas.authn.attributeRepository.jdbc[0].failFast=true
    
    
    
    

    2、客户端接收服务端的详细参数

       String email =   AssertionHolder.getAssertion().getPrincipal().getAttributes().get("email").toString();
            String address =   AssertionHolder.getAssertion().getPrincipal().getAttributes().get("address").toString();
            logger.info("4、id邮箱和地址:"+id+"|"+email+"|"+address);
    

    整个 controller如下

    package com.hlj.sso.client.one.controller;
    
    
    import org.jasig.cas.client.authentication.AttributePrincipal;
    import org.jasig.cas.client.util.AbstractCasFilter;
    import org.jasig.cas.client.util.AssertionHolder;
    import org.jasig.cas.client.validation.Assertion;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import javax.servlet.http.HttpServletRequest;
    
    @Controller
    public class HomeController {
    
        Logger logger = LoggerFactory.getLogger(HomeController.class);
        @GetMapping("hello")
        @ResponseBody
        public Logger home(HttpServletRequest request){
    
    
    
            String remoteUser =request.getRemoteUser();
            logger.info("1、request.getRemoteUser()"+remoteUser);
    
    
            //断言
            Assertion assertion = (Assertion) request.getSession().getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
            AttributePrincipal principal = assertion.getPrincipal();
            String username =  principal.getName();
            logger.info("2、AttributePrincipal.getName"+username);
    
    
            String user3 = AssertionHolder.getAssertion().getPrincipal().getName();
            logger.info("3、AssertionHolder.getAssertion().getPrincipal().getName()"+user3);
    
            /**
             * 加上 其他参数以后的
             */
            String id =   AssertionHolder.getAssertion().getPrincipal().getAttributes().get("id").toString();
    
            String email =   AssertionHolder.getAssertion().getPrincipal().getAttributes().get("email").toString();
            String address =   AssertionHolder.getAssertion().getPrincipal().getAttributes().get("address").toString();
            logger.info("4、id邮箱和地址:"+id+"|"+email+"|"+address);
            return  null;
    
        }
    
        @GetMapping("url")
        @ResponseBody
        public String url(){
            return  "这个路径没有被拦截";
        }
    }
    
    
    

    3、浏览器访问测试,登陆成功。客户端控制台如下

    http://casclientone:8081/hello

    [外链图片转存失败(img-aEyhVCFi-1566552889532)(https://raw.githubusercontent.com/HealerJean123/HealerJean123.github.io/master/blogImages/QQ20180311-192118@2x.png)]

    6、代码下载

    7、完成这一文章实属不易,希望能多多支持,能打赏就更好了

    ContactAuthor

    展开全文
  • springboot 使用校验框架validation校验

    万次阅读 多人点赞 2017-05-14 18:53:20
    b/s系统中对http请求数据的校验多数在客户端进行,这也是出于简单及用户体验性上考虑,但是在一些安全性要求高的系统中服务端校验可缺少的。 Spring3支持JSR-303验证框架,JSR-303 是Java EE 6 中的一项子规范...

    b/s系统中对http请求数据的校验多数在客户端进行,这也是出于简单及用户体验性上考虑,但是在一些安全性要求高的系统中服务端校验是不可缺少的。
    Spring3支持JSR-303验证框架,JSR-303 是Java EE 6 中的一项子规范,叫做BeanValidation,官方参考实现是hibernate Validator(与Hibernate ORM 没有关系),JSR 303 用于对Java Bean 中的字段的值进行验证。

    validation与 springboot 结合

    依赖

    	 <dependency>
                <groupId>javax.validation</groupId>
                <artifactId>validation-api</artifactId>
                <version>2.0.1.Final</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-validator</artifactId>
                <version>6.0.16.Final</version>
            </dependency>
    

    1. bean 中添加标签

    部分代码:
    标签需要加在属性上,@NotBlank 标签含义文章末尾有解释

    public class User {
    	private Integer id;
    	@NotBlank(message = "{user.name.notBlank}")
    	private String name;
    	private String username;
    

    2. Controller中开启验证

    在Controller 中 请求参数上添加@Validated 标签开启验证

        @RequestMapping(method = RequestMethod.POST)
        public User create(@RequestBody @Validated User user) {
    		return userService.create(user);
        }
    
      @RequestMapping(method = RequestMethod.GET)
        public User  getUserById(@NotNull(message = "id不能为空")  int userId)  {
           return userService.getUserById(userId);
        }
    

    3. resource 下新建错误信息配置文件

    当然 message 信息也可以配置在标签后面例如

    public class User {
    	private Integer id;
    	@NotBlank(message = "名字不能为空")
    	private String name;
    	private String username;
    

    也可以在resource 目录下新建提示信息配置文件“ValidationMessages.properties“ 这样可以全局统一管理错误消息

    注意:名字必须为“ValidationMessages.properties“ 因为SpringBoot自动读取classpath中的ValidationMessages.properties里的错误信息
    

    ValidationMessages.properties 文件的编码为ASCII。数据类型为 key value 。key“user.name.notBlank“为第一步 bean的标签 大括号里面对应message的值

    value 为提示信息 ,但是是ASCII 。(内容为“名字不能为空“)

    这里写图片描述

    4. 自定义异常处理器,捕获错误信息

    当验证不通过时会抛异常出来,异常的message 就是 ValidationMessages.properties 中配置的提示信息。此处定义异常处理器。捕获异常信息(因为验证不通过的项可能是多个所以统一捕获处理),并抛给前端。(此处是前后端分离开发)

    
    	@ExceptionHandler(MethodArgumentNotValidException.class)
        public void MethodArgumentNotValidException(Exception ex, HttpServletRequest request, HttpServletResponse response) {
            logger.error( ":" + CommonUtil.getHttpClientInfo(request), ex);
            MethodArgumentNotValidException c = (MethodArgumentNotValidException) ex;
            List<ObjectError> errors =c.getBindingResult().getAllErrors();
            StringBuffer errorMsg=new StringBuffer();
            errors.stream().forEach(x -> errorMsg.append(x.getDefaultMessage()).append(";"));
            pouplateExceptionResponse(response, HttpStatus.INTERNAL_SERVER_ERROR, errorMsg.toString());
        }
    
    
     private void pouplateExceptionResponse(HttpServletResponse response, HttpStatus errorCode, String errorMessage) {
            try {
                response.sendError(errorCode.value(), errorMessage);
            } catch (IOException e) {
                logger.error("failed to populate response error", e);
            }
        }
    
    

    5. 附上部分标签含义

    限制 说明
    @Null 限制只能为null
    @NotNull 限制必须不为null
    @AssertFalse 限制必须为false
    @AssertTrue 限制必须为true
    @DecimalMax(value) 限制必须为一个不大于指定值的数字
    @DecimalMin(value) 限制必须为一个不小于指定值的数字
    @Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
    @Future 限制必须是一个将来的日期
    @Max(value) 限制必须为一个不大于指定值的数字
    @Min(value) 限制必须为一个不小于指定值的数字
    @Past 限制必须是一个过去的日期
    @Pattern(value) 限制必须符合指定的正则表达式
    @Size(max,min) 限制字符长度必须在min到max之间
    @Past 验证注解的元素值(日期类型)比当前时间早
    @NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
    @NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
    @Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

    示例

     @Pattern(regexp="^[a-zA-Z0-9]+$",message="{account.username.space}")
     @Size(min=3,max=20,message="{account.username.size}")
    
    

    如果上述的参数校验不满足要求可以 考虑自定义注解

    自定义注解校验

    步骤:1、定义注解,2、实现校验逻辑

    用法

    
    public class MySaveArgs {
        @NotEmpty
        @MustBeMyCode
        private String code;
    
    

    定义注解

    @Constraint(
            validatedBy = {MyCodeConstraintValidator.class}
    )
    @Target({ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MustBeMyCode {
    
        String message() default "编码校验不通过";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    }
    

    实现ConstraintValidator 接口,编写自己的校验逻辑,

    public class MyCodeConstraintValidator implements ConstraintValidator<MustBeMyCode, String> {
      
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
    		//此处编写自己的校验逻辑,并返回
            return value != null;
        }
    }
    

    注意:ConstraintValidator<MustBeMyCode, String> 此处应填写你自己的校验注解名 和 需校验参数类型

    展开全文
  • 集群master之间基于crc16算法,对key进行校验,得到的值对16384取余,就是key的hash slot(槽)值,每个节点各自存储一部分的hash槽值,主从节点之间基于异步复制方式同步数据。 基于redis集群的基本原理,gedis...

    redis3.0之后提供了新的HA的解决方案,即Cluster模式,由多个节点组成的集群模式。集群master之间基于crc16算法,对key进行校验,得到的值对16384取余,就是key的hash slot(槽)值,每个节点各自存储一部分的hash槽值,主从节点之间基于异步复制方式同步数据。

    基于redis集群的基本原理,gedis需要提供一下方面的能力:

    1、统一的客户端Cluster;

    2、集群连接池的实现;

    3、集群节点的健康检查(后续实现);

    4、负载均衡机制实现;

    5、协议的封装保证对上层透明。

    模型基本设计如下:

    模型设计

    基础模型定义

    /**
     * 节点
     * master:主节点ip+port
     * slaves:从节点ip+port集合
     */
    type Node struct {
    	Url        string
    	Pwd        string
    	InitActive int
    }
    
    type ClusterConfig struct {
    	Nodes             []*Node
    	HeartBeatInterval int
    }
    
    /**
     * 集群客户端
     * heartBeatInterval 心跳检测时间间隔,单位s
     * clusterPool key:连接串 value:连接池
     */
    type Cluster struct {
    	config      *ClusterConfig
    	clusterPool map[string]*ConnPool
    }

     Cluster初始化

    /**
     * 初始化Cluster client
     */
    func NewCluster(clusterConfig ClusterConfig) *Cluster {
    	nodes := clusterConfig.Nodes
    
    	var cluster Cluster
    	clusterPool := make(map[string]*ConnPool)
    
    	for _, node := range nodes {
    		var config = ConnConfig{node.Url, node.Pwd}
    		pool, _ := NewConnPool(node.InitActive, config)
    		clusterPool[node.Url] = pool
    	}
    	cluster.config = &clusterConfig
    	cluster.clusterPool = clusterPool
    	//初始化节点健康检测线程
    	defer func() {
    		go cluster.heartBeat()
    	}()
    	if m==nil {
    		m = new(sync.RWMutex)
    	}
    	return &cluster
    }

    节点心跳检测

    cluster创建后,开启异步线程定时轮询各个节点,向节点发出ping请求,若未响应pong,则表示当前节点异常,然后将当前节点退出连接池,并将该节点加入失败队列,定时轮询队列,检测是否恢复连接,若恢复,则重新创建连接池,从失败队列中退出当前节点。

    /**
     * 连接池心跳检测,定时ping各个节点,ping失败的,从连接池退出,并将节点加入失败队列
     * 定时轮询失败节点队列,检测节点是否已恢复连接,若恢复,则重新创建连接池,并从失败队列中移除
     */
    func (cluster *Cluster) heartBeat() {
    	clusterPool := cluster.GetClusterPool()
    	interval := cluster.config.HeartBeatInterval
    	if interval <= 0 {
    		interval = defaultHeartBeatInterval
    	}
    	var nodes = make(map[string]*Node)
    
    	for i := 0; i < len(cluster.GetClusterNodesInfo()); i++ {
    		node := cluster.GetClusterNodesInfo()[i]
    		nodes[node.Url] = node
    	}
    
    	var failNodes = make(map[string]*Node)
    	for {
    		for url, pool := range clusterPool {
    			result, err := executePing(pool)
    			if err != nil {
    				log.Printf("节点[%s] 健康检查异常,原因[%s], 节点将被移除\n", url, err)
    				//加锁
    				m.Lock()
    				time.Sleep(time.Duration(5)*time.Second)
    				failNodes[url] = nodes[url]
    				delete(clusterPool, url)
    				m.Unlock()
    			} else {
    				log.Printf("节点[%s] 健康检查结果[%s]\n", url, result)
    			}
    		}
    		//恢复检测
    		recover(failNodes, clusterPool)
    
    		time.Sleep(time.Duration(interval) * time.Second)
    	}
    }
    
    /**
     * 检测fail节点是否已恢复正常
     */
    func recover(failNodes map[string]*Node, clusterPool map[string]*ConnPool) {
    	for url,node:=range failNodes{
    		conn := Connect(url)
    		if conn != nil {
    			//节点重连,恢复连接
    			var config = ConnConfig{url, node.Pwd}
    			pool, _ := NewConnPool(node.InitActive, config)
    			//加锁
    			m.Lock()
    			clusterPool[node.Url] = pool
    			delete(failNodes,url)
    			m.Unlock()
    			log.Printf("节点[%s] 已重连\n", url)
    		}
    	}
    }

     测试结果:

    loadbalance目前仅实现随机模式,每次访问前随机选择一个节点进行通信

    func (cluster *Cluster) RandomSelect() *ConnPool {
    	m.RLock()
    	defer m.RUnlock()
    	pools := cluster.GetClusterPool()
    	for _,pool:= range pools{
    		if pool !=nil{
    			return pool
    		}
    	}
    	fmt.Errorf("none pool can be used")
    	return nil
    }

    通信模块的大致流程如下:

    1、cluster随机选择一个健康的节点,进行访问;

    2、如果节点返回业务数据则通信结束;

    3、如果节点返回的消息协议上满足“-MOVED”,例如 -MOVED 5678 127.0.0.1,则表明当前数据不在该节点;

    4、重定向到redis指定的节点访问;

    func (cluster *Cluster) Set(key string, value string) (interface{}, error) {
    	result, err := executeSet(cluster.RandomSelect(), key, value)
    	if err.Error() != protocol.MOVED {
    		return result, err
    	}
    
    	//重定向到新的节点
    	return executeSet(cluster.SelectOne(result.(string)), key, value)
    }
    
    func executeSet(pool *ConnPool, key string, value string) (interface{}, error) {
    	conn, err := GetConn(pool)
    	if err != nil {
    		return nil, fmt.Errorf("get conn fail")
    	}
    	defer pool.PutConn(conn)
    	result := SendCommand(conn, protocol.SET, protocol.SafeEncode(key), protocol.SafeEncode(value))
    	return handler.HandleReply(result)
    }

    这样,对于应用层来讲,无论访问的哪个节点,都能得到最终的结果,相对是透明的。

    调用测试:

    package main
    
    import (
    	. "client"
    	"net"
    	"fmt"
    )
    
    func main() {
    	var node7000 = Node{"127.0.0.1:7000", "123456", 10}
    	var node7001 = Node{"127.0.0.1:7001", "123456", 10}
    	var node7002 = Node{"127.0.0.1:7002", "123456", 10}
    	var node7003 = Node{"127.0.0.1:7003", "123456", 10}
    	var node7004 = Node{"127.0.0.1:7004", "123456", 10}
    	var node7005 = Node{"127.0.0.1:7005", "123456", 10}
    
    	nodes := []*Node{&node7000, &node7001, &node7002, &node7003, &node7004, &node7005}
    	var clusterConfig = ClusterConfig{nodes,10}
    	cluster := NewCluster(clusterConfig)
    	value,err:=cluster.Get("name")
    	fmt.Println(value, err)
    }

     响应结果:

    项目地址:

    https://github.com/zhangxiaomin1993/gedis

    展开全文
  • 1、用户输入数据的校验1、校验方式a:客户端校验。(防君子防了小人)在页面中写js脚本。 这样做的好处是: 输入错误的话提醒比较及时; 减轻服务器的压力 b、服务器端校验。 数据安全 ,整个应用阻止非法数据的...

    1、用户输入数据的校验

    1、校验方式

    a:客户端校验。(防君子防不了小人)在页面中写js脚本。 这样做的好处是:

    • 输入错误的话提醒比较及时;
    • 减轻服务器的压力

    b、服务器端校验

    • 数据安全 ,整个应用阻止非法数据的最后防线

    而在实际开发中通常选择:a+b

    2、服务器端数据校验:

    1、编程式校验:自己Action中编写一个校验代码(缺点:验证规则都写在了代码中)

    前提:动作类继承ActionSupport,重写Validateable接口中的validate()方法 ,在该方法中完成验证。

    步骤如下:
    * validate()方法在其他的业务方法之前执行

    • 验证出错转向的页面
      struts.xml配置<result name="input">/validate/login.jsp</result>
      其中input转向是在action中已经定义好的.
         public static final String INPUT = "input"; 
    • 什么时候表示验证出错(转向input所指向的页面)
      • this.addFieldError("sss", "错误信息");方法指向的是一个集合
      • 当集合不为空时,转向错误页面.显示错误Jsp页面: 使用<s:fielderror/>显示错误消息

    1、针对动作类中的所有动作进行校验

    编写UserAction继承 ActionSupport,并validate()方法。

    package com.itheima.actions;
    
    import java.util.Arrays;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.struts2.interceptor.validation.SkipValidation;
    import com.opensymphony.xwork2.ActionSupport;
    
    public class UserAction extends ActionSupport {
        private String username;
        private String password;
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
    
    public String add(){
        //调用Service
        try{
            System.out.println("保存成功:"+this);
            return SUCCESS;
        }catch(Exception e){
            e.printStackTrace();
            return ERROR;
        }
    }
    
        //在此处编写校验规则:针对所有的动作方法进行验证
    
    public void validate() {
        //用户名不能为null或“”
        if(StringUtils.isEmpty(username)){
            addFieldError("username", "用户名不能为空");//   Map<String,String>key:字段名,value错误提示
        }
    }
        @Override
        public String toString() {
            return "UserAction [username=" + username + ", password=" + password
                    + ", age=" + age + ", birthday=" + birthday + ", hobby="
                    + Arrays.toString(hobby) + "]";
        }
    
    }
    

    在struts.xml中配置action

    <package name="p1" extends="struts-default">
            <action name="UserAdd" class="com.itheima.actions.UserAction" method="add">
                <result>/success.jsp</result>
                <result name="error">/error.jsp</result>
                <result name="input">/regist.jsp</result>
            </action>
    </package>

    分别编写三个简单的页面success.jsp–error.jsp—register.jsp
    register.jsp

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    <%@ taglib uri="/struts-tags" prefix="s"%>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
      <head>
        <title>用户注册</title>
      </head>
    
      <body>
        <s:fielderror></s:fielderror>
        <form action="${pageContext.request.contextPath}/UserAdd.action" method="post">
            姓名:<input type="text" name="username"/><br/>
            密码:<input type="text" name="password"/><br/>
            年龄:<input type="text" name="age"/><br/>
        </form>
      </body>
    </html>

    error.jsp

    <body>
      服务器忙
    </body>

    success.jsp

      <body>
        保存成功
      </body>

    上述的校验方法对action的所有方法进行校验,如果要校验指定的方法,则要

    2、针对指定的动作进行校验

    方式一:写了一个validate方法,可以在不需要验证的动作方法前,使用@SkipValidation。比如动作方法add,可以表示为

    
    //@SkipValidation//忽略验证
    public String add(){
        //调用Service
        try{
            System.out.println("保存成功:"+this);
            return SUCCESS;
        }catch(Exception e){
            e.printStackTrace();
            return ERROR;
        }
    }

    方式二:validate方法有一定的书写规范。public void validate动作方法名(首字母大写),比如动作方法edit

    public String edit(){
        return SUCCESS;
    }

    对应的验证方法

    //只针对edit动作方法进行验证
    public void validateEdit() {
        //用户名不能为null或“”
        if(StringUtils.isEmpty(username)){
            addFieldError("username", "用户名不能为空,咋不听话呢");//   
        }
    }

    validateXxx()只会校验action中方法名为Xxx的方法。其中Xxx的第一个字母要大写。

    底层代码(ValidationInterceptor拦截器)

    这里写图片描述

    总结:所有验证不通过或转换不同的过的,框架都会转向name=”input”的结果视图。要显示错误提示,
    使用<s:fieldError/>,显示字段有关的错误提示

    上述的校验的流程:

    1. 类型转换器对请求参数执行类型转换,并把转换后的值赋给action中的属性。

    2. 如果在执行类型转换的过程中出现异常,系统会将异常信息保存到ActionContext,conversionError拦截器将异常信息封装到fieldErrors里,然后执行第3步。如果类型转换没有出现异常,则直接进入第3步。

    3. 系统通过反射技术调用action中的validateXxx()方法,Xxx为方法名。

    4. 调用action中的validate()方法。

    5. 经过上面4步,如果系统中的fieldErrors存在错误信息(即存放错误信息的集合的size大于0),系统自动将请求转发至名称为input的视图。如果系统中的fieldErrors没有任何错误信息,系统将执行action中的处理方法。

    2、 声明式校验:通过xml配置文件(方便)

    1、针对动作类中的所有动作进行校验
    在这个校验文件中,对UserAction中字符串类型的username属性进行验证,首先要求调用trim()方法去掉空格,然后判断用户名是否为空。

    该文件需要和action类放在同一个包下,文件的取名应遵守ActionClassName-validation.xml规则,其中ActionClassName为action 的简单类名,-validation为固定写法。 例如:如果Action类为cn.msg.validate.UserAction. 那么该文件 的取名应为:UserAction-validation.xml

    UserAction-validation.xml为文件的配置如下

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE validators PUBLIC
            "-//Apache Struts//XWork Validator 1.0.3//EN"
            "http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
    <validators>
        <validate>
        <!-- 写法形式一:可以给一个字段添加好多的验证规则 -->
        <field name="username"><!-- 验证的字段名 -->
            <field-validator type="requiredstring"><!-- 不能为空 -->
                <param name="trim">false</param>
                <message>用户名不能为空哦</message>
            </field-validator>
            <field-validator type="stringlength"><!-- 验证字符串长度的 -->
                <param name="minLength">3</param>
                <param name="maxLength">9</param>
                <message>用户名必须介于${minLength}~${maxLength}之间哦</message>
            </field-validator>
        </field>
        <field name="email">
            <field-validator type="email">
                <message>请输入正确的邮箱</message>
            </field-validator>
        </field>
        <validator type="expression">
            <param name="expression">
                password==repassword
            </param>
            <message>两次密码必须一致</message>
        </validator>
    
        <!-- 写法形式二:用于非字段性的验证
        <validator type="requiredstring">
            <param name="fieldName">username</param>
            <message>用户名不能为空</message>
        </validator>
         -->
    </validators>

    **说明:

    • <validators>: 根元素
    • <field>:指定action中要校验的属性,name属性指定将被验证的表单字段的名字
    • <field-validator>:指定校验器, type 指定验证规则
      上面指定的校验器requiredstring是由系统提供的,系统提供了能满足大部分验证需求
      的校验器,这些校验器的定义可以在xwork-2.x.jar中的
      com.opensymphony.xwork2.validator.validators下的default.xml中找到。

    这里写图片描述

    • <param>:子元素可以向验证程序传递参数
    • <message>:子元素为校验失败后的提示信息,如果需要国际化,可以为message
      指定key属性,key的值为属性文件中的key。

    2、针对指定的动作进行校验

    方式一:使用@SkipValidation

    方式二:声明文件遵循一定的书写规范:
    如果你只需要对UserAction中的某个add方法实施校验,那么,校验文件的取名应为:ActionClassName-ActionName-validation.xml,其中ActionName为struts.xml中action的名称。
    UserAction-UserAdd-validation.xml

    基于XML校验的一些特点

    当为某个action提供了ActionClassName-validation.xml和ActionClassName-ActionName-validation.xml两种规则的校验文件时,系统按下面顺序寻找校验文件:
    1。AconClassName-validation.xml
    2。ActionClassName-ActionName-validation.xml
    系统寻找到第一个校验文件时还会继续搜索后面的校验文件,当搜索到所有校验文件时,会把校验文件里的所有校验规则汇总,然后全部应用于处理方法的校验。如果两个校验文件中指定的校验规则冲突,则只使用后面文件中的校验规则。

    编写校验文件时,不能出现提示信息

    在编写ActionClassName-validation.xml校验文件时,如果出现不了帮助信息,可以按下面方式解决:
    windwos->preferences->myeclipse->files and editors->xml->xmlcatalog
    点“add”,在出现的窗口中的location中选“File system”,然后在xwork-2.1.2解压目录的src\java目录中选择xwork-validator-1.0.3.dtd,回到设置窗口的时候不要急着关闭窗口,应把窗口中的Key Type改为URI 。Key改为http://www.opensymphony.com/xwork/xwork-validator-1.0.3.dtd

    校验器介绍

    • required 必填校验器
    <field-validator type="required">
           <message>性别不能为空!</message>
    </field-validator>
    • requiredstring 必填字符串校验器
    <field-validator type="requiredstring">
           <param name="trim">true</param>
           <message>用户名不能为空!</message>
    </field-validator>
    • stringlength:字符串长度校验器
    <field-validator type="stringlength">
        <param name="maxLength">10</param>
        <param name="minLength">2</param>
        <param name="trim">true</param>
        <message><![CDATA[产品名称应在2-10个字符之间]]></message>
    </field-validator>
    • int:整数校验器
    <field-validator type="int">
        <param name="min">1</param>
        <param name="max">150</param>
        <message>年龄必须在1-150之间</message>
    </field-validator>
    • date: 日期校验器
    <field-validator type="date">
        <param name="min">1900-01-01</param>
        <param name="max">2050-02-21</param>
        <message>生日必须在${min}到${max}之间</message>
    </field-validator>
    • url: 网络路径校验器
    <field-validator type="url">
        <message>传智播客的主页地址必须是一个有效网址</message>
    </field-validator>
    • email:邮件地址校验器
    <field-validator type="email">
        <message>电子邮件地址无效</message>
    </field-validator>
    
    • regex:正则表达式校验器
    <field-validator type="regex">
         <param name="expression"><![CDATA[^13\d{9}$]]></param>
         <message>手机号格式不正确!</message>
    </field-validator>
    • fieldexpression : 字段表达式校验
    <field-validator type="fieldexpression">
           <param name="expression"><![CDATA[(password==repassword)]]></param>
           <message>两次密码输入不一致</message>
    </field-validator>

    3、自定义声明式校验

    自定义验证程序必须实现 Validator 接口.

    Validation 拦截器负责加载和执行各种验证程序. 在加载了一个验证程序之后, 这个拦截器将调用那个验证程序的 setValidatorContext 方法, 把当前的 ValidatorContext 对象传递给它, 这使程序员可以访问当前 Action. 接下来, Validation 拦截器将调用 validate 方法并把需要验证的对象传递给它. validate 方法是编写一个自定义的验证程序时需要覆盖的方法.

    ValidatorSupport 和 FieldValidatorSupport 实现了 Validator 接口

    • 若需要普通的验证程序, 可以继承 ValidatorSupport
    • 若需要字段验证程序, 可以继承 FieldValidatorSupport
    • 若验证程序需要接受一个输入参数, 需要为这个参数增加一个相应的属性

    案例:验证密码的强度
    开发步骤:
    1、编写一个StrongPasswordValidator类:实现Validator接口或者继承ValidatorSupport。如果是验证表单字段有关,建议继承FieldValidatorSupport。

    package com.itheima.actions;
    
    import com.opensymphony.xwork2.validator.ValidationException;
    import com.opensymphony.xwork2.validator.validators.FieldValidatorSupport;
    
    public class StrongPasswordValidator extends FieldValidatorSupport {
    
        private int minLength  = -1;
    
        public int getMinLength() {
            return minLength;
        }
        public void setMinLength(int minLength) {
            this.minLength = minLength;
        }
        //验证方法:针对不符合要求的内容进行判断,向Map中添加信息即可
        //参数:object就是当前的动作对象
        public void validate(Object object) throws ValidationException {
            //获取要验证的字段名称
            String fieldName = getFieldName();
            Object fieldValue = getFieldValue(fieldName, object);
            if(fieldValue==null)
                return;
            if(!isStrong((String)fieldValue)){
                addFieldError(fieldName, object);
            }
            if((minLength>-1)&&((String)fieldValue).length()<minLength){
                // 添加一个消息
                addFieldError(fieldName, object);
            }
    
    
        }
    
         //判断s是否强大
        private static final String GROUP1 = "abcdefghijklmnopqrstuvwxyz";
        private static final String GROUP2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        private static final String GROUP3 = "0123456789";
        //判断密码是否强壮:至少一个大写字母、一个小写字母、一个数字
        private boolean isStrong(String s) {
            boolean ok1 = false;
            boolean ok2 = false;
            boolean ok3 = false;
            int length = s.length();
            for(int i=0;i<length;i++){
                if(ok1&&ok2&&ok3)
                    break;
                String character = s.substring(i,i+1);
                if(GROUP1.contains(character)){
                    ok1 = true;
                    continue;
                }
                if(GROUP2.contains(character)){
                    ok2 = true;
                    continue;
                }
                if(GROUP3.contains(character)){
                    ok3 = true;
                    continue;
                }
            }
            return ok1&&ok2&&ok3;
        }
    
    }
    

    2、要注册校验器
    在WEB-INF\classes目录下,建立一个固定名称为validators.xml的配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE validators PUBLIC
            "-//Apache Struts//XWork Validator Definition 1.0//EN"
            "http://struts.apache.org/dtds/xwork-validator-definition-1.0.dtd">
    
    <!-- START SNIPPET: validators-default -->
    <validators>
        <validator name="strongpassword" class="com.itheima.actions.StrongPasswordValidator"/>
    </validators>

    3、可以像使用requiredstring这样的验证器来使用了

    这里写图片描述

    2、Struts2的国际化

    之前讲到过国际化

    ResourceBundle rb = ResourceBundle.getBunle(“message”,Locale);

    这里讲在struts2中,先准备资源文件,资源文件的命名格式如下:
    baseName_language_country.properties
    baseName_language.properties
    baseName.properties
    其中baseName是资源文件的基本名,我们可以自定义,但language和country必须是java支持的语言和国家。如:
    中国大陆: baseName_zh_CN.properties
    美国: baseName_en_US.properties

    现在为应用添加两个资源文件:
    第一个存放中文:msg_zh_CN.properties

    hello=\u60A8\u5403\u4E86\u5417\u554A\u6CA1\u5403
    username=\u7528\u6237\u540D

    第二个存放英语(美国): msg_en_US.properties
    内容为:

    hello=good morning
    username=Username

    对于中文的属性文件,我们编写好后,应该使用jdk提供的native2ascii命令把文件转换为unicode编码的文件。命令的使用方式如下:

    native2ascii  源文件.properties  目标文件.properties

    1、配置全局国际化消息资源包

    a、配置全局消息资源包
    当准备好资源文件之后,我们可以在struts.xml中通过struts.custom.i18n.resources常量把资源文件定义为全局资源文件,如下:

    <constant name="struts.custom.i18n.resources" value="msg" />

    msg为资源文件的基名。

    b、如何访问

    • 在动作类中:
      前提,动作类继承ActionSupport
    package com.itheima.actions;
    
    import com.opensymphony.xwork2.ActionSupport;
    //在动作类中访问国际化消息
    public class I18nAction extends ActionSupport {
        public String execute(){
            String value = getText("hello");// TextProvider
            System.out.println(value);
            return SUCCESS;
        }
    }
    • 在页面中:
        <!-- 取不到,就显示key值 -->
        <s:text name="hello"></s:text><br/>

    或者

        <s:textfield key="username"></s:textfield><br/>
    • 自由指定消息资源包,借助struts2的有关国际化的标签:
      <s:i18n name="message"><!-- 指定消息资源包的位置 -->
            <s:text name="hello"></s:text>
    </s:i18n>

    如果消息资源包在com.itheima.resources.msg_zh_CN.properties

      <s:i18n name="com/itheima/resources/message"><!-- 指定消息资源包的位置 -->
            <s:text name="hello"></s:text>
    </s:i18n>

    2、配置局部消息资源包

    一定要经过Action才行:
    书写规范:在动作类所在包中,建立名字”动作类名-zh-CN.properties”的配置文件。动作类中访问,发现局部的比全局的优先级高。

    这里写图片描述

    3、包范围的消息资源包

    也得经过action访问
    书写有规范的,名称为package_zh_CN.properties,放在类的包中。可以被包中及子包的所有动作类来访问。
    当查找指定key的消息时,系统会先从ActionClassName_language_country.properties资源文件查找,如果没有找到对应的key,然后沿着当前包往上查找基本名为package 的资源文件,一直找到最顶层包。如果还没有找到对应的key,最后会从常量struts.custom.i18n.resources指定的资源文件中寻找。

    顺序如下

    这里写图片描述

    展开全文
  •  Validation(springmvc使用校验方式: 使用Hibernate Validator(和Hibernate的ORM没有任何关系) ) 拦截器(用于权限控制) 数据回显 需求 表单提交出现错误,重新回到表单,用户重新填写数据,刚才提交的...
  • Web services 是一种很有前途的技术,在面向服务的...尽管现在的 web service 标准规范中包括了提供异步服务的内容,但客户端应用程序前景的细节还有一些混乱和模糊。 Web services 回调是实现这些异步服务的一个重要因
  • Web services 是一种很有前途的技术,在面向服务的架构( ...尽管现在的 web service 标准规范中包括了提供异步服务的内容,但客户端应用程序前景的细节还有一些混乱和模糊。 Web services 回调是实现这些异步服务的一
  • 1.8.6之后的openSDK需要支持Universal Links(通用链接)跳转,否则会出现“微信登录失败,universal link 校验不通过”的错误信息。 对于通过链接的介绍和接入方式,这里不再赘述,具体步骤可以参考各大论坛下...
  • SpringBoot集成CAS客户端

    千次阅读 2018-07-17 14:12:25
    SpringBoot集成CAS客户端实现单点登录功能 一、使用第三方的starter 依赖的 jar 包: &lt;!-- CAS --&gt; &lt;dependency&gt; &lt;groupId&gt;net.unicon.cas&lt;/groupId&gt; ...
  • RocketMQ客户端配置详解

    万次阅读 2019-06-11 22:54:21
    RocketMQ的客户端和服务端采取完全一样的配置机制——客户端没有配置文件,所有的配置选项需要开发者使用对应的配置的setter进行设置。由于现在网上基本找到一套较为完善的配置文档,今天来讲讲截止至RocketMQ ...
  • Spring MVC Bean 参数校验 @Validated

    千次阅读 2017-05-11 10:33:57
    对于DAO层和客户端验证支持不在我们示例范围,忽略,感兴趣的同学可以参考《hibernate validator reference》(有中文)。   在测试支持大家需要准备好如下jar包: validation-api-1.0.0.GA.jar ...
  • HTML5 - 表单客户端验证

    千次阅读 2016-03-17 14:39:48
    过去对于客户端的表单验证,通常是使用JavaScript验证脚本,要么自己写,要么使用第三方库。而在HTML5中,提供了一套客户端...目前HTML5不支持指定验证的时间,而且验证消息的样式和内容各个浏览器不大一样,能修改
  • HTTPS 与 TLS证书链校验

    2020-06-23 17:09:01
    HTTPS协议详解 从事移动互联网软件开发的小伙伴肯定... 这一期间如果你去面试,了解Https的握手过程,都不好意思讲工资。 ...这篇文章以Wireshark抓包,详细了解Https请求中TLS的握手过程 与 客户端证书校验过程。 H
  • 03以太坊客户端

    千次阅读 2018-08-02 13:08:12
    以太坊客户端是实现以太坊规范并通过对等网络与其他以太坊客户端进行通信的软件应用程序。不同的以太坊客户端如果符合参考规范和标准化通信协议,就可以互操作。...开源意味着仅仅是免费使用。这也...
  • Struts2 类型转换和数据校验

    千次阅读 2017-06-12 06:23:33
    学习内容Ø Struts 2类型转换Ø Struts 2数据校验能力目标Ø 熟悉Struts 2自带类型转换器Ø 能自定义类型转换器Ø 自定义输入校验功能Ø 熟悉Struts 2内置校验器本章简介Struts 2提供了功能强大的类型转换器来...
  • 请求参数的校验是很多新手开发非常容易犯错,或存在较多改进点...比如绕过前端程序,直接模拟客户端请求,这时候就会突然在前端预设的各种限制,直击各种数据访问接口,使得我们的系统存在安全隐患。 大量地使用if/e...
  • Struts2中的校验框架

    千次阅读 2012-08-24 06:06:28
    ...尽管这种支持比较弱,但采用Struts2中的客户端校验时需要注意以下几点 1..将的validate属性设置为TRUE 2..能将的theme属性设置为simple 3..建议将的action和namespace属性分开写 4..
  • 虽然很多时候一个api接口的业务,数据逻辑是后端提供的,但真正使用这个接口的是客户端,一个前端功能的实现流程与逻辑,有时候只有客户端的RD才清楚,从某种意义来说,客户端算是接口的需求方。所以建议在前期接口...
  • php控制客户端缓存

    千次阅读 2016-05-05 16:21:30
    http消息中请求头是浏览器要告诉服务器的信息,而响应头是服务器要告诉客户端的信息。这些头的定义是HTTP协议定义好的,客户端和服务器通过解析消息头中信息来完成各自的工作...q=0.8 告诉服务器客户端能接收的语言。2
  • 客户端缓存和服务器缓存处理

    千次阅读 2016-03-26 00:15:27
    客户端缓存和服务器缓存处理 一、概述   缓存的思想可以应用在软件分层的各个层面。它是一种内部机制,对外界而言,是可感知的。   数据库本身有缓存,持久层也可以缓存。(比如:hibernate,还分1级和2级缓存...
  • ASP.NET MVC 5 - 给数据模型添加校验

    千次阅读 2014-03-26 15:46:46
    ASP.NET MVC 的核心设计...给ASP.NET MVC 和 Entity Framework Code First 提供验证支持是 DRY 信条的一次伟大实践。您可以在一个地方 (模型类) 中以声明的方式指定验证规则,这个规则会在应用程序中的任何地方执行。
  • 框架 day69 SpringMVC高级(Validation校验,数据回显,上传,json数据交互,RESTful支持,拦截器)
  • 客户端的角度设计后端的接口

    千次阅读 2017-01-09 17:32:16
    这样设计的好处是客户端和后端可以设计一套统一的分页列表模版代码,即使需求变更,也可以很好的支持。 4.命名规范 统一命名: 与后端约定好即可(php和js在命名时一般采用下划线风格,而...
  • Struts2后台校验两种方式

    千次阅读 2017-06-13 08:58:33
    也称之为客户端校验,主要是通过JavaScript编程的方式进行表单数据的验证。   特点:数据效验在客户端浏览器完成,效率高!安全! 后台校验   也称之为服务器端校验,这里指的是使用Struts2通过XML...
  • 主要内容:客户端接收SYNACK、发送ACK,完成连接的建立。 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd   客户端主动建立连接时,发送SYN段后,连接的状态变为SYN_SENT。 此时如果收到SYNACK段,处理...
  • springmvc 使用JSR-303进行数据校验

    千次阅读 2016-08-30 12:33:08
    项目中,通常使用较多的是前端的校验,比如...在服务端控制层controller校验区分客户端类型。 业务层service(使用较多):主要校验关键业务参数,仅限于service接口中使用的参数。 持久层dao:一般是不校验的。 环
  • Http客户端常规使用说明

    千次阅读 2018-08-07 14:01:27
    Http客户端常规使用说明 ...被定制打包在Android SDK中,可以在Android客户端中非常方便地使用. Http客户端常规使用说明 HTTP协议简单说明 Apache HttpClient(AHC)基础 连接 缓存 Session&amp;Coo...
  • spring MVC自定义校验

    千次阅读 2017-07-05 22:29:39
    SpringMVC自身对数据在服务端的校验有一个比较好的支持,它能将我们提交到服务端的数据按照我们事先的约定进行数据有效性验证,对于合格的数据信息SpringMVC会把它保存在错误对象中,这些错误信息我们也可以通过S

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 71,749
精华内容 28,699
关键字:

当前客户端不支持校验