【SSM】Kisso实用教程

链接:Kisso实例项目

版本:1.4

官方文档:kisso 帮助文档

Maven依赖项

/pom.xml


    com.baomidou
    mybatis-plus
    1.2.12
       

    com.baomidou
    kisso
    3.6.6


    com.alibaba
    fastjson
    1.2.9


    org.bouncycastle
    bcprov-jdk15on
    1.54

Spring MVC设置

/resource/spring-mvc.xml



    
    
    
    
    
    
    
    
    
        
        
        
          

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




    
    
        
        
        
    

    
    

    
    
        insert into user(username,
        password) values(#{username}, #{password})
    
    
    
    
    
    
    

登录时,根据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 getUserInfoByName(User user);
    
    /**
     * 查询所有用户的信息
     * @return 用户信息的表
     */
    List 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 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 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 findAllUser() {
        return userMapper.findAllUser();
    }
}

用户注册

/controller/UserController.java

@Login(action = Action.Skip)
@RequestMapping(value = "/reg", method = RequestMethod.POST)
public @ResponseBody Map addUser(
    @RequestParam(value = "username") String username,
    @RequestParam(value = "password") String password) {
    Map map = new HashMap();
        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 login(
    @RequestParam(value = "username") String username,
    @RequestParam(value = "password") String password,
    @RequestParam(value = "verify") String verify) {
        Map map = new HashMap();
        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 filters = new ArrayList();
            
            //摆动干扰
            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




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

请输入你的用户名和密码

点击查看验证码

代码验证 / java

/controller/UserController.java

@Login(action = Action.Skip)
@RequestMapping(value = "/login", method = RequestMethod.POST)
    public @ResponseBody Map login(
    @RequestParam(value = "username") String username,
    @RequestParam(value = "password") String password,
    @RequestParam(value = "verify") String verify) {
        Map map = new HashMap();
        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() {
    /**
     * 

* SSO 退出,清空退出状态即可 *

* *

* 子系统退出 SSOHelper.logout(request, response); 注意 sso.properties 包含 退出到 * SSO 的地址 , 属性 sso.logout.url 的配置 *

*/ 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

登出

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" %>

${ userid },欢迎光临!

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实用教程)