【SSM】Kisso實(shí)用教程

鏈接:Kisso實(shí)例項(xiàng)目

版本:1.4

官方文檔:kisso 幫助文檔

Maven依賴項(xiàng)

/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設(shè)置

/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" />
    <!-- 測試模式 ,不同環(huán)境配置選擇設(shè)置 -->
    <property name="runMode" value="test_mode" />
    <!-- 此處可以注入 SSOConfig 配置屬性,也可以定義自己的 kisso 插件,基礎(chǔ) 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設(shè)置

/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映射設(shè)置

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

登錄時,根據(jù)username,獲取User類。

加鹽密碼=MD5(用戶名+原密碼)

User映射接口

/mapper/UserMapper.java

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

Java Bean

/model/User.java

帶有mybatis-plus.jar提供的注解,用于導(dǎo)出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服務(wù)接口

/service/UserService.java

注意:Controller中帶有Autowired注解的字段必須為接口。

public interface UserService {
    
    /**
     * 添加用戶
     * @param user 用戶
     * @return 修改的行數(shù)
     */
    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服務(wù)實(shí)現(xiàn)

/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", "驗(yàn)證碼錯誤");
            return map;
        }
        request.getSession().removeAttribute("verify");
        /**
         * 生產(chǎn)環(huán)境需要過濾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 設(shè)置 cookie 同時改變 jsessionId
             */
            SSOToken st = new SSOToken(request);
            st.setId(userid);
            st.setUid(username_);
            st.setType(1);

            // 記住密碼,設(shè)置 cookie 時長 1 天 = 86400 秒 【動態(tài)設(shè)置 maxAge 實(shí)現(xiàn)記住密碼功能】
            /*
             * String rememberMe = req.getParameter("rememberMe"); if
             * ("on".equals(rememberMe)) {
             * request.setAttribute(SSOConfig.SSO_COOKIE_MAXAGE, 86400); }
             */
            request.setAttribute(SSOConfig.SSO_COOKIE_MAXAGE, -1);//瀏覽器關(guān)閉自動刪除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,規(guī)范參數(shù)格式
  2. 判斷驗(yàn)證碼,從Session中取
  3. WafRequestWrapper過濾SQL注入
  4. 將用戶名和密碼放入Java Bean,即User
  5. 調(diào)用UserService的驗(yàn)證機(jī)制
  6. 查看結(jié)果
  7. 如果驗(yàn)證失敗,則返回失敗
  8. 如果成功,則新建SSOToken,放入useridusername
  9. SSOHelper.setSSOCookie(request, response, st, true)完成SSO注冊
  10. 最后,用戶的Cookie中,有一項(xiàng)uid是加密的,保存了用戶的useridusername

注意點(diǎn):

  • 為什么是uidsso.properties中的sso.cookie.name項(xiàng)
  • 怎么加密?密鑰在sso.properties中的sso.secretkey

驗(yàn)證機(jī)制

控制器

/controller/UserController.java

/**
 * 驗(yàn)證碼 (注解跳過權(quán)限驗(yàn)證)
 */
@Login(action = Action.Skip)
@ResponseBody
@RequestMapping("/verify")
public void verify() {
    try {
        String verifyCode = CaptchaUtil.outputImage(response.getOutputStream());
        request.getSession().setAttribute("verify", verifyCode);//把驗(yàn)證碼存入session
        logger.debug(String.format("verify code: %s", verifyCode));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

注意點(diǎn):

  • 地址是 #{controller}/verify,設(shè)為Action.Skip,因?yàn)槿魏稳硕伎梢垣@取驗(yàn)證碼,不寫則默認(rèn)為Action.Normal啟用認(rèn)證。

  • 將驗(yàn)證碼的明文存入Session中,待驗(yàn)證登錄時取出。

繪圖

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

public class CaptchaUtil {
    public static String outputImage(OutputStream out) throws IOException {
            ConfigurableCaptchaService cs = new ConfigurableCaptchaService();
            //驗(yàn)證碼寬高
            cs.setWidth(85);
            cs.setHeight(35);
            
            //設(shè)置 6 位自適應(yīng)驗(yàn)證碼
    //      AdaptiveRandomWordFactory arw = new AdaptiveRandomWordFactory();
    //      arw.setMinLength(6);
    //      arw.setMaxLength(6);
    //      cs.setWordFactory(arw);
            
            //字符大小設(shè)置
            RandomFontFactory rf = new RandomFontFactory();
            rf.setMinSize(25);
            rf.setMaxSize(28);
            cs.setFontFactory(rf);
            
            //文本渲染
    //      cs.setTextRenderer(new RandomYBestFitTextRenderer());
            
            //設(shè)置一個單一顏色字體
            cs.setColorFactory(new SingleColorFactory(new Color(59, 162, 9)));
    //      cs.setFilterFactory(new CurvesRippleFilterFactory(cs.getColorFactory()));
    
            
            //圖片濾鏡設(shè)置
            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));
            
            //輸出驗(yàn)證圖片
            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取參函數(shù) / 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];
        }
    }

初始化驗(yàn)證 / 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 : "請輸入驗(yàn)證碼",
                    minlength : "驗(yàn)證碼長度為4個字符"
                }
            }
        });
    });

設(shè)置提交方式 / js

$.validator.setDefaults({
        submitHandler : function() {
            $.post(
            // 接收數(shù)據(jù)的頁面
            'login',
            // 傳給后臺的數(shù)據(jù),多個參數(shù)用&連接或者使用json格式數(shù)據(jù):{a:'value1',b:'value2'}
            {
                username : $("#username").val(),
                password : $("#password").val(),
                verify : $("#verify").val()
            }, function(data) {
                if (data.code == '200') {
                    alert("msg: " + data.msg + "\n" + "即將跳轉(zhuǎn)。");
                    location.href = gup("ReturnURL");
                } else if (data.code == '400') {
                    alert(data.msg);
                    location.reload();
                }
            },
            // 默認(rèn)返回字符串,設(shè)置值等于json則返回json數(shù)據(jù)
            'json').error(function() {
                alert("登錄失敗,請稍后再試。");
            });
        }
    });

設(shè)置表單 / 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">驗(yàn)證碼</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="點(diǎn)擊查看驗(yàn)證碼">
        </p>
        <p>
            <input class="reset" type="reset" value="重置"> <input
                class="submit" type="submit" value="登錄">
        </p>
    </fieldset>
</form>

代碼驗(yàn)證 / 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", "驗(yàn)證碼錯誤");
            return map;
        }
        request.getSession().removeAttribute("verify");
        // 其他登錄認(rèn)證機(jī)制...
    }

登出

/controller/UserController.java

@RequestMapping(value = "/logout")
public String logout() {
    /**
     * <p>
     * SSO 退出,清空退出狀態(tài)即可
     * </p>
     * 
     * <p>
     * 子系統(tǒng)退出 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:/";
}

觸發(fā)登出事件:利用<a href='logout'></a>即可。

注意點(diǎn):

  • 使用SSOHelper.clearLogin(request, response)

重定向

/WebContent/jsp/login.jsp

添加地址取參函數(shù)

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];
    }
}

跳轉(zhuǎn)回登錄前的頁面

Kisso攔截器將未授權(quán)訪問重定向至登錄頁,帶ReturnURL參數(shù),存放跳轉(zhuǎn)前地址,登錄成功后,自動跳回。

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

注意點(diǎn):

  • 返回200時,為成功,跳轉(zhuǎn)
  • 返回400時,為失敗,刷新頁面

登出的重定向

點(diǎn)擊鏈接登出時,服務(wù)器返回302重定向。

HTML

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

JAVA

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

注意點(diǎn):

  • 適用ajax。瀏覽器中的js跳轉(zhuǎn),地址可以從服務(wù)器寫,如/ssm
  • 適用a href。服務(wù)器的302、301跳轉(zhuǎn),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";

注意點(diǎn):

  • 模版引擎除了JSTL外還有Velocity等。Velocity充分體現(xiàn)了的MVC思想。
  • 顯示用戶名的流程。

MVC簡易流程:

  1. 控制層:利用Kisso獲取用戶信息,放入模型。
  2. 模型層:存放、傳遞數(shù)據(jù)。
  3. 視圖層:根據(jù)模型,解析數(shù)據(jù),渲染頁面。

常見問題

ContextLoader類不存在

項(xiàng)目 -> 屬性 -> 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問題,在項(xiàng)目屬性中的Project Facets
  • 修改容器名稱,即localhost:8080/????,項(xiàng)目屬性中的Web Project Settings
  • Java文件錯誤,修改Java Compiler,即編譯器版本
  • 注意文件名和路徑的大小寫
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容