一.数据库建表
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>
<update id="updateTokenExpire" parameterType="Map">update demo_member set token_expire=#{token_expire} where account =#{account}</update></mapper> 三.shiro.xml<!-- 每次请求接口 更新token过期时间 -->
针对以上shiro配置相应的ShiroRealm.java如下<?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>
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; } }
六.springmvc.xmlpublic 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; } }
拦截器配置
七.AuthTokenInterceptor.java<!-- 拦截器配置 --> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/"/> <mvc:mapping path="/v1/**"/> <bean class="com.demo.interceptors.AuthTokenInterceptor"/> </mvc:interceptor> </mvc:interceptors>
这里只是做了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
以上关于memberService的方法调用都与mybatis中的查询一一对应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\"}"; } }
九.web.xml
网上有很多关于spring springmvc shiro相关的web.xml配置文件信息 再次就不再赘述
注意shiro需要过滤器过滤即可
十.相关jar包下载
http://download.csdn.net/detail/chundonghan/9824537 这里jar包相对较新 可能会有与低版本不兼容的情况