当前位置: 首页 > 工具软件 > kisso > 使用案例 >

【SSM】Kisso实用教程(二)

蒋奇
2023-12-01

https://www.jianshu.com/p/4026edd340bb

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,放入userid和username
  9. 用SSOHelper.setSSOCookie(request, response, st, true)完成SSO注册
  10. 最后,用户的Cookie中,有一项uid是加密的,保存了用户的userid和username

注意点:

  • 为什么是uid? sso.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中,待验证登录时取出。

绘图

/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:/";
}

触发登出事件:利用即可。
注意点:使用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,即编译器版本
  • 注意文件名和路径的大小写
 类似资料: