spring springmvc shiro 实现单点登录demo

一.数据库建表

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包相对较新 可能会有与低版本不兼容的情况










  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值