鏈接: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設(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ù)實現(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", "驗證碼錯誤");
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 實現(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;
}
登錄的邏輯:
-
@RequestParam
憔儿,規(guī)范參數(shù)格式 - 判斷驗證碼忆植,從
Session
中取 - 用
WafRequestWrapper
過濾SQL注入 - 將用戶名和密碼放入Java Bean,即
User
中 - 調(diào)用
UserService
的驗證機制 - 查看結(jié)果
- 如果驗證失敗谒臼,則返回失敗
- 如果成功朝刊,則新建
SSOToken
,放入userid
和username
- 用
SSOHelper.setSSOCookie(request, response, st, true)
完成SSO注冊 - 最后蜈缤,用戶的
Cookie
中拾氓,有一項uid
是加密的,保存了用戶的userid
和username
注意點:
- 為什么是
uid
底哥?sso.properties
中的sso.cookie.name
項 - 怎么加密咙鞍?密鑰在
sso.properties
中的sso.secretkey
驗證機制
控制器
/controller/UserController.java
/**
* 驗證碼 (注解跳過權(quán)限驗證)
*/
@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
,設(shè)為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);
//設(shè)置 6 位自適應(yīng)驗證碼
// 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));
//輸出驗證圖片
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];
}
}
初始化驗證 / 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個字符"
}
}
});
});
設(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();
}
},
// 默認返回字符串症杏,設(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">驗證碼</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 退出卢鹦,清空退出狀態(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>
即可。
注意點:
- 使用
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();
}
注意點:
- 返回200時驻呐,為成功灌诅,跳轉(zhuǎn)
- 返回400時芳来,為失敗,刷新頁面
登出的重定向
點擊鏈接登出時猜拾,服務(wù)器返回302重定向即舌。
HTML
<p><a href="tologout">登出</a></p>
JAVA
@RequestMapping(value = "/logout")
public String logout() {
// SSO清理工作
// ...
return "redirect:/";
}
注意點:
- 適用
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";
注意點:
- 模版引擎除了
JSTL
外還有Velocity
等。Velocity
充分體現(xiàn)了的MVC思想蚪燕。
- 顯示用戶名的流程娶牌。
MVC簡易流程:
- 控制層:利用Kisso獲取用戶信息,放入模型馆纳。
- 模型層:存放诗良、傳遞數(shù)據(jù)。
- 視圖層:根據(jù)模型鲁驶,解析數(shù)據(jù)鉴裹,渲染頁面。
常見問題
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荸频,即編譯器版本
- 注意文件名和路徑的大小寫