基于springside4 的app token登录鉴权

       很多公司app客户端和服务端通讯是基于socket自定义私有协议,但对于创业型公司和需要快速搭建app原型的公司,http通讯是比较好的选择,开发效率高,http协议会比socket协议通信慢,且容易被刷协议和截取数据包。那么基于http协议的通信,登录和鉴权是比较重要的环节。最近在研究springside4,shiro,基于这些框架搭建了一套app http登录和鉴权。

下图先介绍整个逻辑流程:



登录服务端代码:

@Controller

@RequestMapping(value = "/loginuser")

public classUserLogin {

 

   @Autowired

   protected AccountServiceaccountService;

 

   @Autowired

   private CacheManagerehcacheManager;

 

   /**

    * 设定安全的密码,生成随机的salt并经过1024sha-1 hash

    */

   private StringentryptPassword(String salt,String plainPassWord) {

 

      byte[] decodeSalt=Encodes.decodeHex(salt);

      byte[] hashPassword =Digests.sha1(plainPassWord.getBytes(), decodeSalt, Constants.HASH_INTERATIONS);

      return Encodes.encodeHex(hashPassword);

   }

   //@RequestMapping(value = "/loginUser", method= RequestMethod.POST)

   @RequestMapping(value = "/{username}/{password}", method = RequestMethod.GET)

   @ResponseBody

   public String log(@PathVariable("username")String userName,@PathVariable("password") String passWord) {

 

      //根据用户名查找数据库

      Useruser = accountService.findUserByLoginName(userName);

      if (user !=null) {

         if(user.getStatus().equals("disabled")) {

            throw newDisabledAccountException();

         }

         //数据库存的密码为加密后的,需要把客户端传过来的密码明文进行加密

         StringdescPassWord=entryptPassword(user.getSalt(),passWord);

         if(user.getPassword().equals(descPassWord))

         {

            //根据KEY,用户ID,当前时间戳生成token

            StringclientToken=HmacSHA256Utils.digest(Constants.KEY, String.valueOf(user.getId()+System.currentTimeMillis()));

           

            //存放在ehcache,可以改为存放在redis

            Elementelement = newElement(user.getId(), clientToken);

            Cachecache=ehcacheManager.getCache(Constants.LOGIN_TOKEN_CACHE_NAME);

            cache.put(element);

            //返回token给客户端

            return clientToken;

         }

         else

            return null;

      } 

      else

         return null;

    

   }

}

 

 
public class Constants {
    public static final String PARAM_DIGEST = "digest";
    public static final String PARAM_USERNAME = "username";
    public static final String PARAM_ID = "id";

    public static final String LOGIN_TOKEN_CACHE_NAME = "logintoken";

    //摘要,用来加密生成token
    public static String KEY="qwertyasdfoimdfdk";

    public static final String HASH_ALGORITHM = "SHA-1";
    public static final int HASH_INTERATIONS = 1024;
}

 

服务端鉴权使用filter组装数据后委托shiro 的realm处理

 

public classStatelessAuthcFilterextendsAccessControlFilter {

 

      @Autowired

      private CacheManagerehcacheManager;

  

      @Override

       protected boolean isAccessAllowed(ServletRequestrequest, ServletResponse response, Object mappedValue)throws Exception {

           return false;

       }

 

       @Override

       protected boolean onAccessDenied(ServletRequestrequest, ServletResponse response)throws Exception {

           //客户端的url的数字签名

           String clientDigest =request.getParameter(Constants.PARAM_DIGEST);

           //2、客户端传入的用户身份

           String username =request.getParameter(Constants.PARAM_USERNAME);

           String id =request.getParameter(Constants.PARAM_ID);         

          

           HttpServletRequesthttpRequest=(HttpServletRequest) request;        

           //删除掉URL的数字签名

           String digestParm="digest="+clientDigest;

           String url=httpRequest.getRequestURL()+"?"+httpRequest.getQueryString().replace("&"+digestParm,"").replace(digestParm,"");

              

           //4、生成无状态Token

           StatelessToken token = new StatelessToken(Long.parseLong(id),username,url, clientDigest);

 

           try {

               //5、委托给Realm进行登录

               getSubject(request,response).login(token);

           } catch (Exception e) {

               e.printStackTrace();

               onLoginFail(response); //6、登录失败

               return false;

           }

           return true;

       }

 

       //登录失败时默认返回401状态码

       private void onLoginFail(ServletResponse response) throws IOException {

           HttpServletResponse httpResponse =(HttpServletResponse) response;

          httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

           httpResponse.getWriter().write("login error");

       }

}

 

 

 

public classStatelessRealm extends AuthorizingRealm {

   protected AccountServiceaccountService;

 

   @Autowired

   private CacheManagerehcacheManager;

   @Override

   publicbooleansupports(AuthenticationToken token) {

        //仅支持StatelessToken类型的Token

        return tokeninstanceof StatelessToken;

   }

   @Override

   protectedAuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        //根据用户名查找角色,请根据需求实现

        SimpleAuthorizationInfoauthorizationInfo =  newSimpleAuthorizationInfo();

        authorizationInfo.addRole("admin");

        return authorizationInfo;

   }

   @Override

   protectedAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)throws AuthenticationException{

        StatelessToken statelessToken =(StatelessToken) token;

        String username =statelessToken.getUsername();//用户名

        long id=statelessToken.getId();//用户ID

        Cache cache=ehcacheManager.getCache(Constants.LOGIN_TOKEN_CACHE_NAME);

        Element tokenElement=cache.get(id);

        String strtoken=(String)tokenElement.getObjectValue();

       

        //在服务器端生成客户端参数消息摘要

        String url=statelessToken.getUrl();

        

        String serverDigest = HmacSHA256Utils.digest(strtoken,url);

        System.out.println("ClientDigest:"+statelessToken.getClientDigest());

        System.out.println("serverDigest"+serverDigest);

        //然后进行客户端消息摘要和服务器端消息摘要的匹配

        return new SimpleAuthenticationInfo(

                username,

                serverDigest,

                getName());

         

   }

}

 

 

 

客户端测试代码如下

 

/**

 * Hello world!

 *

 */

public classApp

{

   static RestTemplaterestTemplate =new RestTemplate();

   publicstaticfinalStringPARAM_DIGEST="digest";

   publicstaticfinalStringPARAM_USERNAME="username";   

   public static String userName="admin";

   public static String passWord="admin";

   public static String userId="1";

   publicstaticvoidmain( String[] args )

   {

     

      //登录获取token

        String token =login(); 

       

        MultiValueMap<String, String>params = newLinkedMultiValueMap<String, String>(); 

        params.add(PARAM_USERNAME,userName);

        params.add("id", userId); 

 

        //构造请求URL

        String url = UriComponentsBuilder 

                .fromHttpUrl("http://localhost:8080/showcase/api/v1/user/1.json"

               .queryParams(params).build().toUriString();

        //URL进行签名

        String digest=HmacSHA256Utils.digest(token,url);

       

        //URL上带上签名

        params.add(PARAM_DIGEST, digest);

        url= UriComponentsBuilder 

                .fromHttpUrl("http://localhost:8080/showcase/api/v1/user/1.json"

               .queryParams(params).build().toUriString(); 

       

        System.out.println("url:"+url);

        ResponseEntity<String>responseEntity = restTemplate.getForEntity(url, String.class);

        System.out.println("responseEntity:"+responseEntity.getBody());

  

   }

   

   /**

     * 登录返回token

     * @return

     */

   publicstaticString login()

   {

 

      String loginUrl="http://localhost:8080/showcase/loginuser/"+userName+"/"+passWord;  

        ResponseEntity<String>responseEntity = restTemplate.getForEntity(loginUrl, String.class);

        return (String)responseEntity.getBody();       

   }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值