美文网首页程序员
【SSM】Kisso实用教程

【SSM】Kisso实用教程

作者: bajdcc | 来源:发表于2016-12-08 08:16 被阅读0次

    链接:Kisso实例项目

    版本:1.4

    官方文档:kisso 帮助文档

    Maven依赖项

    /pom.xml

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus</artifactId>
        <version>1.2.12</version>
    </dependency>       
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>kisso</artifactId>
        <version>3.6.6</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.9</version>
    </dependency>
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-jdk15on</artifactId>
        <version>1.54</version>
    </dependency>
    

    Spring MVC设置

    /resource/spring-mvc.xml

    <!--  kisso 注入初始化,也支持使用 web.xml 初始化 -->
    <bean id="kissoInit" class="com.baomidou.kisso.web.WebKissoConfigurer" init-method="initKisso">
        <property name="ssoPropPath" value="sso.properties" />
        <!-- 测试模式 ,不同环境配置选择设置 -->
        <property name="runMode" value="test_mode" />
        <!-- 此处可以注入 SSOConfig 配置属性,也可以定义自己的 kisso 插件,基础 SSOPlugin 抽象类。
        <property name="pluginList">
            <list>
                <bean name="com.xxxx.MyPlugin">
            </list>
            </property>
        -->
        </bean>
        <mvc:interceptors>
        <!-- SSO 拦截器 -->
        <!-- path 对所有的请求拦截使用/**,对某个模块下的请求拦截使用:/myPath/* -->
        <mvc:interceptor>
            <mvc:mapping path="/user/*" />
            <mvc:mapping path="/permission/*" />
            <bean class="com.baomidou.kisso.web.interceptor.SSOSpringInterceptor" />
        </mvc:interceptor>      
    </mvc:interceptors>
    

    Kisso设置

    /resource/sso.properties

    ################ SSOConfig file #################
    sso.encoding=utf-8
    sso.secretkey=30eb4892122c45fd0f
    sso.cookie.name=uid
    sso.cookie.domain=.vcap.me
    sso.login.url=http://ssm.vcap.me:8080/ssm/user/tologin
    

    或者

    ################ SSOConfig file #################
    sso.encoding=utf-8
    sso.secretkey=30eb4892122c45fd0f
    sso.cookie.name=uid
    sso.cookie.domain=127.0.0.1
    sso.login.url=http://127.0.0.1:8080/ssm/user/tologin
    

    domain不能为localhost,可修改hosts使用自定义域名。

    User映射设置

    /mapper/userMapper.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.mapper.UserMapper">
        <!-- 解决表名与字段不匹配 -->
        <resultMap type="User" id="userResultMap">
            <result property="userid" column="userid" />
            <result property="username" column="username" />
            <result property="password" column="password" />
        </resultMap>
    
        <!-- 查询用户是否存在 -->
        <select id="checkUserByUsername" resultType="int" parameterType="java.lang.String">
            select count(1) from user WHERE username=#{username}
        </select>
    
        <!-- 添加用户 -->
        <insert id="addUser" parameterType="User">
            insert into user(username,
            password) values(#{username}, #{password})
        </insert>
        
        <!-- 获取用户信息 -->
        <select id="getUserInfoByName" resultType="User" parameterType="User">
            select * from user WHERE username=#{username}
        </select>
        
        <!-- 查询所有用户-->
        <select id="findAllUser" resultType="User">
            select * from user
        </select>
    </mapper>
    

    登录时,根据username,获取User类。

    加盐密码=MD5(用户名+原密码)

    User映射接口

    /mapper/UserMapper.java

    public interface UserMapper {
        
        /**
         * 添加用户
         * @param user 用户
         * @return 修改的行数
         */
        int addUser(User user);
        
        /**
         * 查询用户是否存在
         * @param username 用户名
         * @return 
         */
        int checkUserByUsername(String username);
        
        /**
         * 根据用户名返回用户信息
         * @param user 用户名
         * @return 用户信息
         */
        List<User> getUserInfoByName(User user);
        
        /**
         * 查询所有用户的信息
         * @return 用户信息的表
         */
        List<User> findAllUser();
    }
    

    Java Bean

    /model/User.java

    带有mybatis-plus.jar提供的注解,用于导出SQL语句。

    import com.baomidou.mybatisplus.annotations.TableField;
    import com.baomidou.mybatisplus.annotations.TableId;
    public class User {
    
        @TableField(exist = false)
        private static final long serialVersionUID = 1L;
    
        /** 主键ID */
        @TableId
        private Long userid;
    
        private String username;
        private String password;;
    
        public User() {
            super();
        }
    
        public Long getId() {
            return userid;
        }
    
        public void setId(Long id) {
            this.userid = id;
        }
    
        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;
        }
    
    }
    

    User服务接口

    /service/UserService.java

    注意:Controller中带有Autowired注解的字段必须为接口

    public interface UserService {
        
        /**
         * 添加用户
         * @param user 用户
         * @return 修改的行数
         */
        int addUser(User user);
        
        /**
         * 查询用户是否存在
         * @param username 用户名
         * @return 
         */
        boolean checkUserByUsername(String username);
        
        /**
         * 检查用户名和密码是否合法
         * @param user 登录信息
         * @return 成功则返回id,失败返回-1
         */
        long validUserAndPassword(User user);
        
        /**
         * 查询所有用户的信息
         * @return 用户信息的表
         */
        List<User> findAllUser();
    }
    

    User服务实现

    /service/impl/UserServiceImpl.java

    @Service
    @Transactional
    public class UserServiceImpl implements UserService {
    
        @Resource
        public UserMapper userMapper;
    
        @Override
        public int addUser(User user) {
            int userid = userMapper.addUser(user);
            return userid;
        }
    
        @Override
        public boolean checkUserByUsername(String username) {
            return userMapper.checkUserByUsername(username) == 1;
        }
        
        @Override
        public long validUserAndPassword(User user) {
            List<User> users = userMapper.getUserInfoByName(user);
            if (users.isEmpty()) {
                return -1;// 不存在
            }
            User info = users.get(0);
            if (SaltEncoder.md5SaltValid(user.getUsername(), info.getPassword(), user.getPassword())) {
                return info.getId();
            } else {
                return -1;// 不存在
            }
        }
    
        @Override
        public List<User> findAllUser() {
            return userMapper.findAllUser();
        }
    }
    

    用户注册

    /controller/UserController.java

    @Login(action = Action.Skip)
    @RequestMapping(value = "/reg", method = RequestMethod.POST)
    public @ResponseBody Map<String, Object> addUser(
        @RequestParam(value = "username") String username,
        @RequestParam(value = "password") String password) {
        Map<String, Object> map = new HashMap<String, Object>();
            if (userService.checkUserByUsername(username)) {
                User user = new User();
                user.setUsername(username);
                user.setPassword(SaltEncoder.md5SaltEncode(username, password));
                int id = userService.addUser(user);
                logger.debug(String.format("add user: id=%d name=%s", id, username));
                map.put("code", "200");
                map.put("msg", "注册成功!");
            } else {
                logger.warn(String.format("conflict user: name=%s", username));
                map.put("code", "400");
                map.put("msg", "用户已存在!");
            }
            return map;
        }
    

    SaltEncoder.md5SaltEncode(登录名,原密码)=> 返回哈希密码

    用户登录

    /controller/UserController.java

    @Login(action = Action.Skip)
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public @ResponseBody Map<String, Object> login(
        @RequestParam(value = "username") String username,
        @RequestParam(value = "password") String password,
        @RequestParam(value = "verify") String verify) {
            Map<String, Object> map = new HashMap<String, Object>();
            String verifyCode = String.valueOf(request.getSession().getAttribute("verify"));
            if (!verifyCode.equalsIgnoreCase(verify)) {
                map.put("code", "400");
                map.put("msg", "验证码错误");
                return map;
            }
            request.getSession().removeAttribute("verify");
            /**
             * 生产环境需要过滤sql注入
             */
            WafRequestWrapper req = new WafRequestWrapper(request);
            String username_ = req.getParameter("username");
            String password_ = req.getParameter("password");
            User user = new User();
            user.setUsername(username_);
            user.setPassword(password_);
            long userid = userService.validUserAndPassword(user);
            if (userid != -1) {
                logger.debug(String.format("login success: name=%s password=%s", username_, password_));
                map.put("code", "200");
                map.put("msg", "登录成功!");
    
                /*
                 * authSSOCookie 设置 cookie 同时改变 jsessionId
                 */
                SSOToken st = new SSOToken(request);
                st.setId(userid);
                st.setUid(username_);
                st.setType(1);
    
                // 记住密码,设置 cookie 时长 1 天 = 86400 秒 【动态设置 maxAge 实现记住密码功能】
                /*
                 * String rememberMe = req.getParameter("rememberMe"); if
                 * ("on".equals(rememberMe)) {
                 * request.setAttribute(SSOConfig.SSO_COOKIE_MAXAGE, 86400); }
                 */
                request.setAttribute(SSOConfig.SSO_COOKIE_MAXAGE, -1);//浏览器关闭自动删除cookie
                SSOHelper.setSSOCookie(request, response, st, true);
            } else {
                logger.warn(String.format("wrong login: name=%s password=%s", username_, password_));
                map.put("code", "400");
                map.put("msg", "您输入的帐号或密码有误");
            }
            return map;
        }
    

    登录的逻辑:

    1. @RequestParam,规范参数格式
    2. 判断验证码,从Session中取
    3. WafRequestWrapper过滤SQL注入
    4. 将用户名和密码放入Java Bean,即User
    5. 调用UserService的验证机制
    6. 查看结果
    7. 如果验证失败,则返回失败
    8. 如果成功,则新建SSOToken,放入useridusername
    9. SSOHelper.setSSOCookie(request, response, st, true)完成SSO注册
    10. 最后,用户的Cookie中,有一项uid是加密的,保存了用户的useridusername

    注意点:

    • 为什么是uidsso.properties中的sso.cookie.name
    • 怎么加密?密钥在sso.properties中的sso.secretkey

    验证机制

    控制器

    /controller/UserController.java

    /**
     * 验证码 (注解跳过权限验证)
     */
    @Login(action = Action.Skip)
    @ResponseBody
    @RequestMapping("/verify")
    public void verify() {
        try {
            String verifyCode = CaptchaUtil.outputImage(response.getOutputStream());
            request.getSession().setAttribute("verify", verifyCode);//把验证码存入session
            logger.debug(String.format("verify code: %s", verifyCode));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

    注意点:

    • 地址是 #{controller}/verify,设为Action.Skip,因为任何人都可以获取验证码,不写则默认为Action.Normal启用认证。

    • 将验证码的明文存入Session中,待验证登录时取出。

    绘图

    引用自SpringWind
    来自CaptchaHelper.java中的/com/utils/CaptchaUtil.java

    public class CaptchaUtil {
        public static String outputImage(OutputStream out) throws IOException {
                ConfigurableCaptchaService cs = new ConfigurableCaptchaService();
                //验证码宽高
                cs.setWidth(85);
                cs.setHeight(35);
                
                //设置 6 位自适应验证码
        //      AdaptiveRandomWordFactory arw = new AdaptiveRandomWordFactory();
        //      arw.setMinLength(6);
        //      arw.setMaxLength(6);
        //      cs.setWordFactory(arw);
                
                //字符大小设置
                RandomFontFactory rf = new RandomFontFactory();
                rf.setMinSize(25);
                rf.setMaxSize(28);
                cs.setFontFactory(rf);
                
                //文本渲染
        //      cs.setTextRenderer(new RandomYBestFitTextRenderer());
                
                //设置一个单一颜色字体
                cs.setColorFactory(new SingleColorFactory(new Color(59, 162, 9)));
        //      cs.setFilterFactory(new CurvesRippleFilterFactory(cs.getColorFactory()));
        
                
                //图片滤镜设置
                ConfigurableFilterFactory filterFactory = new ConfigurableFilterFactory();
                List<BufferedImageOp> filters = new ArrayList<BufferedImageOp>();
                
                //摆动干扰
                WobbleImageOp wio = new WobbleImageOp();
                wio.setEdgeMode(AbstractImageOp.EDGE_CLAMP);
                wio.setxAmplitude(2.0);
                wio.setyAmplitude(1.0);
                filters.add(wio);
        
                //曲线干扰
        //      CurvesImageOp cio = new CurvesImageOp();
        //      cio.setColorFactory(new SingleColorFactory(new Color(59, 162, 9)));
        //      cio.setEdgeMode(AbstractImageOp.EDGE_ZERO);
        //      cio.setStrokeMax(0.3f);
        //      cio.setStrokeMin(0.1f);
        //      filters.add(cio);
                
                filterFactory.setFilters(filters);
                cs.setFilterFactory(filterFactory);
                
                //椭圆形干扰背景
        //      cs.setBackgroundFactory(new OvalNoiseBackgroundFactory(7));
                
                //线形干扰背景
                cs.setBackgroundFactory(new LineNoiseBackgroundFactory(37));
                
                //输出验证图片
                return EncoderHelper.getChallangeAndWriteImage(cs, "png", out);
            }
    }
    

    HTML

    /WebContent/jsp/login.jsp

    引入js / html

    <script src="${js_root}/js/jquery-1.11.1.js"></script>
    <script src="${js_root}/js/jquery.validate.min.js"></script>
    <script src="${js_root}/js/messages_zh.js"></script>
    

    gup取参函数 / js

    function gup(name) {
            name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
            var regexS = "[\\?&]" + name + "=([^&#]*)";
            var regex = new RegExp(regexS);
            var results = regex.exec(location.pathname);
            if (results == null) {
                return location.pathname;
            } else {
                return results[1];
            }
        }
    

    初始化验证 / js

    $(document).ready(function() {
            // validate the comment form when it is submitted
            $("#signupForm").validate({
                rules : {
                    username : {
                        required : true,
                        minlength : 2,
                    },
                    password : {
                        required : true,
                        minlength : 6
                    },
                    verify : {
                        required : true,
                        minlength : 4
                    }
                },
                messages : {
                    username : {
                        required : "请输入用户名",
                        minlength : "用户名至少由两个字符组成"
                    },
                    password : {
                        required : "请输入密码",
                        minlength : "密码长度不能小于 6 个字符"
                    },
                    verify : {
                        required : "请输入验证码",
                        minlength : "验证码长度为4个字符"
                    }
                }
            });
        });
    

    设置提交方式 / js

    $.validator.setDefaults({
            submitHandler : function() {
                $.post(
                // 接收数据的页面
                'login',
                // 传给后台的数据,多个参数用&连接或者使用json格式数据:{a:'value1',b:'value2'}
                {
                    username : $("#username").val(),
                    password : $("#password").val(),
                    verify : $("#verify").val()
                }, function(data) {
                    if (data.code == '200') {
                        alert("msg: " + data.msg + "\n" + "即将跳转。");
                        location.href = gup("ReturnURL");
                    } else if (data.code == '400') {
                        alert(data.msg);
                        location.reload();
                    }
                },
                // 默认返回字符串,设置值等于json则返回json数据
                'json').error(function() {
                    alert("登录失败,请稍后再试。");
                });
            }
        });
    

    设置表单 / html

    <form class="cmxform" id="signupForm" method="post" action="login">
        <fieldset>
            <legend>请输入你的用户名和密码</legend>
            <p>
                <label for="cusername">用户名</label> <input id="username"
                    name="username" type="text">
            </p>
            <p>
                <label for="cpassword">密码</label> <input id="password"
                    name="password" type="password">
            </p>
            <p>
                <label for="cverify">验证码</label> <input id="verify" name="verify"
                    type="text"> <img id="verifyImg"
                    onclick="javascript:this.src=('verify?reload='+(new Date()).getTime())"
                    src="verify" width="85" height="35" alt="点击查看验证码">
            </p>
            <p>
                <input class="reset" type="reset" value="重置"> <input
                    class="submit" type="submit" value="登录">
            </p>
        </fieldset>
    </form>
    

    代码验证 / java

    /controller/UserController.java

    @Login(action = Action.Skip)
    @RequestMapping(value = "/login", method = RequestMethod.POST)
        public @ResponseBody Map<String, Object> login(
        @RequestParam(value = "username") String username,
        @RequestParam(value = "password") String password,
        @RequestParam(value = "verify") String verify) {
            Map<String, Object> map = new HashMap<String, Object>();
            String verifyCode = String.valueOf(request.getSession().getAttribute("verify"));
            if (!verifyCode.equalsIgnoreCase(verify)) {
                map.put("code", "400");
                map.put("msg", "验证码错误");
                return map;
            }
            request.getSession().removeAttribute("verify");
            // 其他登录认证机制...
        }
    

    登出

    /controller/UserController.java

    @RequestMapping(value = "/logout")
    public String logout() {
        /**
         * <p>
         * SSO 退出,清空退出状态即可
         * </p>
         * 
         * <p>
         * 子系统退出 SSOHelper.logout(request, response); 注意 sso.properties 包含 退出到
         * SSO 的地址 , 属性 sso.logout.url 的配置
         * </p>
         */
        SSOToken st = SSOHelper.getToken(request);
        if (st != null) {
            logger.debug(String.format("logout: id=%d, uid=%s", st.getId(), st.getUid()));
        }
        SSOHelper.clearLogin(request, response);
        return "redirect:/";
    }
    

    触发登出事件:利用<a href='logout'></a>即可。

    注意点:

    • 使用SSOHelper.clearLogin(request, response)

    重定向

    /WebContent/jsp/login.jsp

    添加地址取参函数

    function gup(name) {
        name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
        var regexS = "[\\?&]" + name + "=([^&#]*)";
        var regex = new RegExp(regexS);
        var results = regex.exec(location.pathname);
        if (results == null) {
            return location.pathname;
        } else {
            return results[1];
        }
    }
    

    跳转回登录前的页面

    Kisso拦截器将未授权访问重定向至登录页,带ReturnURL参数,存放跳转前地址,登录成功后,自动跳回。

    if (data.code == '200') {
        alert("msg: " + data.msg + "\n" + "即将跳转。");
        location.href = gup("ReturnURL");
    } else if (data.code == '400') {
        alert(data.msg);
        location.reload();
    }
    

    注意点:

    • 返回200时,为成功,跳转
    • 返回400时,为失败,刷新页面

    登出的重定向

    点击链接登出时,服务器返回302重定向。

    HTML

    <p><a href="tologout">登出</a></p>
    

    JAVA

    @RequestMapping(value = "/logout")
    public String logout() {
        // SSO清理工作
        // ...
        return "redirect:/";
    }
    

    注意点:

    • 适用ajax。浏览器中的js跳转,地址可以从服务器写,如/ssm
    • 适用a href。服务器的302、301跳转,Controller方法返回String,值为"redirect:path/to/redirect"

    显示用户名

    /WebContent/jsp/index.jsp

    /WebContent/jsp/permission.jsp

    HTML

    <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <!-- other -->
    <p>${ userid },欢迎光临!</p>
    

    JAVA

    SSOToken st = SSOHelper.getToken(request);
    if (st != null) {
        request.setAttribute("userid", st.getUid());
    }
    return "/index";
    

    注意点:

    • 模版引擎除了JSTL外还有Velocity等。Velocity充分体现了的MVC思想。
    • 显示用户名的流程。

    MVC简易流程:

    1. 控制层:利用Kisso获取用户信息,放入模型。
    2. 模型层:存放、传递数据。
    3. 视图层:根据模型,解析数据,渲染页面。

    常见问题

    ContextLoader类不存在

    项目 -> 属性 -> Web Deployment Assembly
    Add => Java Build Path Entries => Maven Dependencies

    缺少类

    修改pom.xml,然后Update Project。

    常用解决办法

    • 清理Tomcat目录
    • 重启Tomcat
    • Classpath路径问题,增加JRE、Tomcat、Maven、Web App Lib
    • Web Module问题,在项目属性中的Project Facets
    • 修改容器名称,即localhost:8080/????,项目属性中的Web Project Settings
    • Java文件错误,修改Java Compiler,即编译器版本
    • 注意文件名和路径的大小写

    相关文章

      网友评论

        本文标题:【SSM】Kisso实用教程

        本文链接:https://www.haomeiwen.com/subject/upwjmttx.html