spring springmvc shiro 实现单点登录demo
2017-04-25 09:27
309 查看
一.数据库建表
CREATE TABLE `demo_member` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`account` varchar(20) NOT NULL DEFAULT '',
`password` varchar(32) NOT NULL DEFAULT '',
`email` varchar(40) NOT NULL DEFAULT '',
`qq` varchar(11) NOT NULL DEFAULT '',
`mobile` varchar(11) NOT NULL DEFAULT '',
`nickname` varchar(20) NOT NULL DEFAULT '',
`realname` varchar(10) NOT NULL DEFAULT '',
`avatar` varchar(200) NOT NULL DEFAULT '',
`gender` smallint(2) NOT NULL DEFAULT '-1' COMMENT '1:M 0:F',
`auth_token` varchar(40) NOT NULL DEFAULT '',
`token_expire` int(11) NOT NULL DEFAULT '0',
`salt` char(6) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
二.mybatis
<?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="MemberMapper">
<!-- 登录 -->
<select id="memberLogin" parameterType="Map" resultType="Map">
SELECT nickname,avatar,gender,auth_token,token_expire,salt FROM demo_member
<where>
account =#{account} AND password=#{password}
</where>
union
SELECT nickname,avatar,gender,auth_token,token_expire,salt FROM demo_member
<where>
email =#{account} AND password=#{password}
</where>
</select>
<!-- 获取验证token -->
<select id="authToken" parameterType="String" resultType="Map">
SELECT auth_token,token_expire FROM demo_member
<where>
account =#{account}
</where>
union
SELECT auth_token,token_expire FROM demo_member
<where>
email =#{account}
</where>
</select>
<!-- 登录更新 token 相关字段 salt:加密用 token_expire:token过期时间 -->
<update id="updateToken" parameterType="Map">
update demo_member set salt=#{salt},auth_token=#{auth_token},token_expire=#{token_expire} where account =#{account}
</update>
三.shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd" default-lazy-init="true">
<!-- ================ Shiro start ================ -->
<!-- 自定义Realm -->
<bean id="ShiroRealm" class="com.demo.interceptors.shiro.ShiroRealm" ></bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="ShiroRealm" />
</bean>
<!-- Shiro Filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager"></property>
<!-- 要求登录时的链接(登录页面地址),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面 -->
<property name="loginUrl" value="/"></property>
<!-- 登录成功后要跳转的连接(本例中此属性用不到,因为登录成功后的处理逻辑在LoginController里硬编码) -->
<!-- <property name="successUrl" value="/" ></property> -->
<!-- 用户访问未对其授权的资源时,所显示的连接 -->
<property name="unauthorizedUrl" value="/"></property>
<property name="filterChainDefinitions">
<value>
<!-- anon:地址不需要任何权限即可访问 -->
/admin/index = anon
<!-- perms["admin:product"] 需要权限为admin:product的用户-->
<!-- roles["admin"] 需要角色为admin的用户
/admin/index roles["admin"]-->
/index/index = authc
/basic/** = authc
<!-- /admin/ = anon
/admin/index.jsp = anon
/admin/login.jsp = authc
/admin/logout.jsp = logout
/admin/common/captcha.jhtml = anon
/admin/product/** = perms["admin:product"]
/admin/** = authc -->
</value>
</property>
</bean>
<!-- ================ Shiro end ================ -->
</beans>针对以上shiro配置相应的ShiroRealm.java如下
public class ShiroRealm extends AuthorizingRealm{
/**
* 权限认证,获取登录用户的权限 (此处对笔者要实现的功能无关紧要,可忽略)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
/**
* 登录认证,创建用户的登录信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取基于用户名和密码的令牌
//实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的
//两个token的引用都是一样的
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token;
String username = usernamePasswordToken.getUsername();
String password = MD5.md5_1(new String(usernamePasswordToken.getPassword()));
Map<String, Object> params = new HashMap<>();
params.put("account", username);
params.put("password", password);
Map<String, Object> member = null;
try
{
member = memberService.memberLogin(params);
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
if(member!=null){
String salt = Salt.salting();
System.out.println("salt:"+salt);
String auth_token = SHA.sha(salt+username,"SHA-1");
System.out.println(auth_token);
long token_expire = System.currentTimeMillis()/1000+10;
System.out.println(token_expire);
params.put("salt", salt);
params.put("auth_token", auth_token);
params.put("token_expire", token_expire);
try
{
memberService.updateToken(params);
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
四.Salt文件
public class Salt
{
private static final String salt[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
public static String salting()
{
StringBuffer s = new StringBuffer();
Random r = new Random();
for (int i = 0; i < 6; i++)
{
s.append(salt[r.nextInt(salt.length)]);
}
return s.toString();
}
public static void main(String[] args)
{
salting();
}
}
五.MD5以及SHA 文件
拦截器配置
<!-- 拦截器配置 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/"/>
<mvc:mapping path="/v1/**"/>
<bean class="com.demo.interceptors.AuthTokenInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>七.AuthTokenInterceptor.java
这里只是做了log
public class AuthTokenInterceptor extends HandlerInterceptorAdapter
{
protected Logger logger = LoggerFactory.getLogger(getClass());
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
HttpSession session = request.getSession();
String account = (String) session.getAttribute("currentUser");
if(account==null){
logger.info("{}","请先登录");
return false;
}
Map<String,Object> auth=memberService.authToken(account);
if(auth==null){
logger.info("{}","无此账号");
return false;
}
if(auth.get("auth_token").equals(session.getAttribute("auth_token"))){
long tokenExpire = (System.currentTimeMillis()/1000);
if((int)auth.get("token_expire")
>= tokenExpire){
Map<String,Object> params = new HashMap<>();
params.put("account", account);
params.put("token_expire", tokenExpire);
memberService.updateTokenExpire(params);
logger.info("{}","token正常");
return true;
}else{
logger.info("{}","token过期");
return false;
}
}else{
logger.info("{}","token失效");
return false;
}
//return super.preHandle(request, response, handler);
}
}
八.controller
Controller
@RequestMapping(value="XX")
public class MemberController extends BaseController
{
@RequestMapping(value = "/login", method = {RequestMethod.GET,RequestMethod.POST})
@ResponseBody
public String memberLogin(@RequestParam(value="account",defaultValue="") String account,
@RequestParam(value="password",defaultValue="") String password)
{
UsernamePasswordToken token = new UsernamePasswordToken(account, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
} catch (IncorrectCredentialsException ice) {
// 捕获密码错误异常
return "{\"message\":\"password error!\"}";
} catch (UnknownAccountException uae) {
// 捕获未知用户名异常
return "{\"message\":\"account error!\"}";
}
return "{\"message\":\"login\"}";
}
}以上关于memberService的方法调用都与mybatis中的查询一一对应
九.web.xml
网上有很多关于spring springmvc shiro相关的web.xml配置文件信息 再次就不再赘述
注意shiro需要过滤器过滤即可
十.相关jar包下载
http://download.csdn.net/detail/chundonghan/9824537 这里jar包相对较新 可能会有与低版本不兼容的情况
CREATE TABLE `demo_member` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`account` varchar(20) NOT NULL DEFAULT '',
`password` varchar(32) NOT NULL DEFAULT '',
`email` varchar(40) NOT NULL DEFAULT '',
`qq` varchar(11) NOT NULL DEFAULT '',
`mobile` varchar(11) NOT NULL DEFAULT '',
`nickname` varchar(20) NOT NULL DEFAULT '',
`realname` varchar(10) NOT NULL DEFAULT '',
`avatar` varchar(200) NOT NULL DEFAULT '',
`gender` smallint(2) NOT NULL DEFAULT '-1' COMMENT '1:M 0:F',
`auth_token` varchar(40) NOT NULL DEFAULT '',
`token_expire` int(11) NOT NULL DEFAULT '0',
`salt` char(6) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
二.mybatis
<?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="MemberMapper">
<!-- 登录 -->
<select id="memberLogin" parameterType="Map" resultType="Map">
SELECT nickname,avatar,gender,auth_token,token_expire,salt FROM demo_member
<where>
account =#{account} AND password=#{password}
</where>
union
SELECT nickname,avatar,gender,auth_token,token_expire,salt FROM demo_member
<where>
email =#{account} AND password=#{password}
</where>
</select>
<!-- 获取验证token -->
<select id="authToken" parameterType="String" resultType="Map">
SELECT auth_token,token_expire FROM demo_member
<where>
account =#{account}
</where>
union
SELECT auth_token,token_expire FROM demo_member
<where>
email =#{account}
</where>
</select>
<!-- 登录更新 token 相关字段 salt:加密用 token_expire:token过期时间 -->
<update id="updateToken" parameterType="Map">
update demo_member set salt=#{salt},auth_token=#{auth_token},token_expire=#{token_expire} where account =#{account}
</update>
<!-- 每次请求接口 更新token过期时间 --><update id="updateTokenExpire" parameterType="Map">update demo_member set token_expire=#{token_expire} where account =#{account}</update></mapper>
三.shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd" default-lazy-init="true">
<!-- ================ Shiro start ================ -->
<!-- 自定义Realm -->
<bean id="ShiroRealm" class="com.demo.interceptors.shiro.ShiroRealm" ></bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="ShiroRealm" />
</bean>
<!-- Shiro Filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager"></property>
<!-- 要求登录时的链接(登录页面地址),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面 -->
<property name="loginUrl" value="/"></property>
<!-- 登录成功后要跳转的连接(本例中此属性用不到,因为登录成功后的处理逻辑在LoginController里硬编码) -->
<!-- <property name="successUrl" value="/" ></property> -->
<!-- 用户访问未对其授权的资源时,所显示的连接 -->
<property name="unauthorizedUrl" value="/"></property>
<property name="filterChainDefinitions">
<value>
<!-- anon:地址不需要任何权限即可访问 -->
/admin/index = anon
<!-- perms["admin:product"] 需要权限为admin:product的用户-->
<!-- roles["admin"] 需要角色为admin的用户
/admin/index roles["admin"]-->
/index/index = authc
/basic/** = authc
<!-- /admin/ = anon
/admin/index.jsp = anon
/admin/login.jsp = authc
/admin/logout.jsp = logout
/admin/common/captcha.jhtml = anon
/admin/product/** = perms["admin:product"]
/admin/** = authc -->
</value>
</property>
</bean>
<!-- ================ Shiro end ================ -->
</beans>针对以上shiro配置相应的ShiroRealm.java如下
public class ShiroRealm extends AuthorizingRealm{
/**
* 权限认证,获取登录用户的权限 (此处对笔者要实现的功能无关紧要,可忽略)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
/**
* 登录认证,创建用户的登录信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取基于用户名和密码的令牌
//实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的
//两个token的引用都是一样的
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token;
String username = usernamePasswordToken.getUsername();
String password = MD5.md5_1(new String(usernamePasswordToken.getPassword()));
Map<String, Object> params = new HashMap<>();
params.put("account", username);
params.put("password", password);
Map<String, Object> member = null;
try
{
member = memberService.memberLogin(params);
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
if(member!=null){
String salt = Salt.salting();
System.out.println("salt:"+salt);
String auth_token = SHA.sha(salt+username,"SHA-1");
System.out.println(auth_token);
long token_expire = System.currentTimeMillis()/1000+10;
System.out.println(token_expire);
params.put("salt", salt);
params.put("auth_token", auth_token);
params.put("token_expire", token_expire);
try
{
memberService.updateToken(params);
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
//后期可以memcached、redis做缓存 存储 以减小服务器压力 this.setSession("currentUser", username); this.setSession("auth_token", auth_token); //this.setSession("token_expire", token_expire); return new SimpleAuthenticationInfo(username, usernamePasswordToken.getPassword(),getName()); } return null; } /** * 将一些数据放到ShiroSession中,以便于其它地方使用 * @see 比如Controller,使用时直接用HttpSession.getAttribute(key)就可以取到 */ private void setSession(Object key, Object value){ Subject currentUser = SecurityUtils.getSubject(); if(null != currentUser){ Session session = currentUser.getSession(); if(null != session){ session.setAttribute(key, value); } } } }
四.Salt文件
public class Salt
{
private static final String salt[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
public static String salting()
{
StringBuffer s = new StringBuffer();
Random r = new Random();
for (int i = 0; i < 6; i++)
{
s.append(salt[r.nextInt(salt.length)]);
}
return s.toString();
}
public static void main(String[] args)
{
salting();
}
}
五.MD5以及SHA 文件
public class MD5 { public static String md5(String str) { try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(str.getBytes()); byte b[] = md.digest(); int i; StringBuffer buf = new StringBuffer(""); for (int offset = 0; offset < b.length; offset++) { i = b[offset]; if (i < 0) i += 256; if (i < 16) buf.append("0"); buf.append(Integer.toHexString(i)); } str = buf.toString(); } catch (Exception e) { e.printStackTrace(); } return str; } public static String md5_1(String str) { try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(str.getBytes()); //str = new BigInteger(1, md.digest()).toString(16); BASE64Encoder base64en = new BASE64Encoder(); str = base64en.encode(md.digest()); } catch (Exception e) { e.printStackTrace(); } return str; } }
public class SHA { public static String sha(String str,String type) { try { MessageDigest md = MessageDigest.getInstance(type); md.update(str.getBytes()); byte byteBuffer[] = md.digest(); StringBuffer strHexString = new StringBuffer(); for (int i = 0; i < byteBuffer.length; i++) { String hex = Integer.toHexString(0xff & byteBuffer[i]); if (hex.length() == 1) { strHexString.append('0'); } strHexString.append(hex); } // 得到返回結果 str = strHexString.toString(); } catch (Exception e) { e.printStackTrace(); } return str; } }六.springmvc.xml
拦截器配置
<!-- 拦截器配置 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/"/>
<mvc:mapping path="/v1/**"/>
<bean class="com.demo.interceptors.AuthTokenInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>七.AuthTokenInterceptor.java
这里只是做了log
public class AuthTokenInterceptor extends HandlerInterceptorAdapter
{
protected Logger logger = LoggerFactory.getLogger(getClass());
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
HttpSession session = request.getSession();
String account = (String) session.getAttribute("currentUser");
if(account==null){
logger.info("{}","请先登录");
return false;
}
Map<String,Object> auth=memberService.authToken(account);
if(auth==null){
logger.info("{}","无此账号");
return false;
}
if(auth.get("auth_token").equals(session.getAttribute("auth_token"))){
long tokenExpire = (System.currentTimeMillis()/1000);
if((int)auth.get("token_expire")
>= tokenExpire){
Map<String,Object> params = new HashMap<>();
params.put("account", account);
params.put("token_expire", tokenExpire);
memberService.updateTokenExpire(params);
logger.info("{}","token正常");
return true;
}else{
logger.info("{}","token过期");
return false;
}
}else{
logger.info("{}","token失效");
return false;
}
//return super.preHandle(request, response, handler);
}
}
八.controller
Controller
@RequestMapping(value="XX")
public class MemberController extends BaseController
{
@RequestMapping(value = "/login", method = {RequestMethod.GET,RequestMethod.POST})
@ResponseBody
public String memberLogin(@RequestParam(value="account",defaultValue="") String account,
@RequestParam(value="password",defaultValue="") String password)
{
UsernamePasswordToken token = new UsernamePasswordToken(account, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
} catch (IncorrectCredentialsException ice) {
// 捕获密码错误异常
return "{\"message\":\"password error!\"}";
} catch (UnknownAccountException uae) {
// 捕获未知用户名异常
return "{\"message\":\"account error!\"}";
}
return "{\"message\":\"login\"}";
}
}以上关于memberService的方法调用都与mybatis中的查询一一对应
九.web.xml
网上有很多关于spring springmvc shiro相关的web.xml配置文件信息 再次就不再赘述
注意shiro需要过滤器过滤即可
十.相关jar包下载
http://download.csdn.net/detail/chundonghan/9824537 这里jar包相对较新 可能会有与低版本不兼容的情况
相关文章推荐
- springmvc+shiro+maven 实现登录认证与权限授权管理
- springmvc+shiro+maven 实现登录认证与权限授权管理 201
- springmvc+shiro+maven 实现登录认证与权限授权管理
- SSO单点登录一(Spring+SpringMVC+固定密码)实现的简单的同域SSOdemo
- springmvc+shiro+maven 实现登录认证与权限授权管理
- Spring boot +spring mvc+shiro 登录验证demo
- SpringBoot/SpringMVC整合Shiro(一):实现登录与注册(MD5加盐加密)
- springmvc+shiro+maven 实现登录认证与权限授权管理
- Shiro+SpringMVC 实现更安全的登录(加密匹配&登录失败超次数锁定帐号)
- 使用SSM框架搭建Web服务器实现登录功能(Spring+SpringMVC+Mybatis)
- shiro+springmvc整合demo下载
- jeesz分布式架构 Dubbo、zookeeper、KafKa、redis、fastdfs、单点登录sso、springmvc+mybatis+shiro、Restful服务
- Spring Boot / Spring MVC 入门实践 (三) : 入门项目介绍与用户注册登录的实现
- Spring MVC +MyBatis +MySQL 登录查询Demo 解决了mybatis异常【转】
- 怎么实现登录之后跳转到登录之前的页面?SpringMVC+Freemarker
- spring+springMVC 整合 MongoDB 实现注册登录
- shiro+cas+spring-data-redis实现多系统单点登录和分布式项目的session同步
- spring+springmvc+Interceptor+jwt+redis实现sso单点登录
- spring+springmvc+Interceptor+jwt+redis实现sso单点登录
- SpringMVC + Mybatis 实现用户登录功能