Web Service Authentication + MD5 [from]

本文介绍了一种使用MD5哈希和时间戳进行密码加密的Web服务认证机制,避免了密码在网络中明文传输,提高了安全性。认证过程中,用户名明文发送,密码通过MD5加密,并加入时间戳防止重放攻击。

Sample Image - WebServiceAuthentication.gif

Introduction

This is a simple mechanism to authenticate users to a Web Service, using a Time Token and MD5 Hashing to encrypt password.

Background

In CodeProject, you can find at least two others' mechanism to authenticate users to a Web Service. Dan_P wrote Authentication for Web Services as a Simple authentication for web services using SOAP headers. But the username and password are send in clear text and there is no encryption for the data. HENDRIK R. is the author of An introduction to Web Service Security using WSE, that is really a complete solution, but too much complicated for my purposes. The username is send in clear text, but it is possible to use Password Digest to encrypt the password. The data are encrypted using XML Encryption specification to encrypt portions of the SOAP messages.

My solution is something in the middle of the above two. The username is send in clear text, but I use MD5 to encrypt the password. I do not need to send sensitive data, so the data returned by the Web Service is not encrypted.

Using the code

The basic idea is to send UserName and Password from the client to the Web Service using MD5 Hash Code as encryption system. In this way, the password never travels in clear over the network. The Web Service retrieves the user password from a DB or anything else and uses the same MD5 algorithm to test if the password is correct. To be sure that if someone intercepts the Hash, this can be used to authenticate in a later time, I added a timestamp before hashing the Key string. Last, as we are not always on the same server and/or the client clock may be in a different Time Zone or simply not synchronized, I added the possibility to request a Token containing the time mark to the server.

I provided a sample in ASP.NET C# for the client side, but it is possible to use any language: ASP classical JScript or VBScript, PHP, Python, etc. Anyway, on the client side we need to build up the Key using UserName, Password and the hashed timestamp Token previously got from the same Web Service. We can then call the Service and we will get the answer (or an authentication failure warning) that is displayed on the web page.


 
None.gif private   void  ButtonUseToken_Click( object  sender, System.EventArgs e)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
string ret;
InBlock.gif    
string UserName, Password, ServiceName, Token;
InBlock.gif    
string Key, ToHash;
InBlock.gif
InBlock.gif    UserName
=this.TextBoxUserName.Text;
InBlock.gif    Password
=this.TextBoxPwd.Text;
InBlock.gif    ServiceName
=this.TextBoxService.Text;
InBlock.gif    Token
=this.TextBoxToken.Text;
InBlock.gif    ToHash
=UserName.ToUpper()+"|"+Password+"|"+Token;
InBlock.gif    Key
=Hash(ToHash)+"|"+UserName;
InBlock.gif
InBlock.gif    ServicePointReference.ServicePoint Authenticate 
= 
InBlock.gif                             
new ServicePointReference.ServicePoint();
InBlock.gif    ret
=Authenticate.UseService(Key, ServiceName);
InBlock.gif    
this.ServResponse.Text=ret;
ExpandedBlockEnd.gif}

The MD5 Hash procedure is very simple in C#; this one was written by Vasudevan Deepak Kumar in Securing Web Accounts.


 
None.gif private   string  Hash( string  ToHash)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
// First we need to convert the string into bytes,
InBlock.gif    
// which means using a text encoder.
InBlock.gif
    Encoder enc = System.Text.Encoding.ASCII.GetEncoder();
InBlock.gif
InBlock.gif    
// Create a buffer large enough to hold the string
InBlock.gif
    byte[] data = new byte[ToHash.Length];
InBlock.gif    enc.GetBytes(ToHash.ToCharArray(), 
0, ToHash.Length, data, 0true);
InBlock.gif
InBlock.gif    
// This is one implementation of the abstract class MD5.
InBlock.gif
    MD5 md5 = new MD5CryptoServiceProvider();
InBlock.gif    
byte[] result = md5.ComputeHash(data);
InBlock.gif
InBlock.gif    
return BitConverter.ToString(result).Replace("-""").ToLower();
ExpandedBlockEnd.gif}

On Web Service server side I implemented just three Web Methods:

GetToken is used to get the Time-marked token. The token you get this way, is intended to be used in the basic Authenticate method, or in the UseService that can also verify the access rights for the users authenticated to the requested service. The core of the system is implemented by TestHash. Here the password is hard-coded, but in the sample provided you have also the code to get it from a database:

minus.gif Collapse

 
None.gif private   bool  TestHash ( string  HashStr, 
None.gif             
string  UserName,  int  minutes,  string  ServiceName)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
string Pwd, ToHash;
InBlock.gif    
string sResult, sResultT, sResultToken;
InBlock.gif    
try
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
// JUST FOR TEST: the password is hard-coded:
InBlock.gif
        Pwd="SeCrEt";
InBlock.gif
InBlock.gif        DateTime dt 
= DateTime.Now;
InBlock.gif        System.TimeSpan minute 
= new System.TimeSpan(0,0,minutes,0,0);
InBlock.gif        dt 
= dt-minute;
InBlock.gif        
//before hashing we have:
InBlock.gif        
//USERNAME|PassWord|YYYYMMDD|HHMM
InBlock.gif
        ToHash=UserName.ToUpper()+"|"+Pwd+"|"+dt.ToString("yyyyMMdd")+
InBlock.gif                                             
"|"+dt.ToString("HHmm");
InBlock.gif        sResult 
= Hash(ToHash);
InBlock.gif        
//TokenWeGotBefore
InBlock.gif
        ToHash=dt.ToString("yyyyMMdd")+"|"+dt.ToString("HHmm");
InBlock.gif        sResultToken 
= Hash(ToHash);
InBlock.gif        
//USERNAME|PassWord|TokenWeGotBefore
InBlock.gif
        ToHash=UserName.ToUpper()+"|"+Pwd+"|"+sResultToken;
InBlock.gif        sResultT 
= Hash(ToHash);
InBlock.gif    
InBlock.gif        
if ((sResult==HashStr) || (sResultT==HashStr)) 
InBlock.gif            
return true;
InBlock.gif        
else
InBlock.gif            
if (minutes==0// allowed max 2 minutes - 1
InBlock.gif                            
// second to call web service
InBlock.gif
            return TestHash (HashStr, UserName, 1, ServiceName);
InBlock.gif        
else
InBlock.gif            
return false;
ExpandedSubBlockEnd.gif    }

InBlock.gif    
catch
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
return false;
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

To request a hashed time-stamped Token to the Web Service the method is:


 
None.gif [WebMethod]
None.gif
public   string  GetToken ()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
string ToHash, sResult;
InBlock.gif    DateTime dt 
= DateTime.Now;
InBlock.gif    ToHash
=dt.ToString("yyyyMMdd")+"|"+dt.ToString("HHmm");
InBlock.gif    sResult 
= Hash(ToHash);
InBlock.gif    
return sResult;
ExpandedBlockEnd.gif}

The method that checks the user authentication is also kept very simple; in a real application you normally need to access a database to check the authentication level and may need to return some data to the caller:

minus.gif Collapse

 
None.gif [WebMethod]
None.gif
public   string  UseService ( string  Key,  string  ServiceName)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
string [] HashArray;
InBlock.gif    
string UserName, level;
InBlock.gif
InBlock.gif    
// Key string: HASH|User|OptionalData
InBlock.gif
    HashArray=Key.Split('|');
InBlock.gif    level 
= "-1";    //defaul level
InBlock.gif

InBlock.gif    
if (TestHash(HashArray[0], HashArray[1], 0, ServiceName))
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
try
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            UserName
=HashArray[1];
InBlock.gif            
// JUST FOR TEST: the User authentication level is hard-coded
InBlock.gif            
// but may/shuold be retrieved from a DataBase
InBlock.gif
            switch (UserName)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
case "MyUserName":
InBlock.gif                    level
="1";
InBlock.gif                    
break;
InBlock.gif                
case "OtherUser":
InBlock.gif                    level
="2";
InBlock.gif                    
break;
InBlock.gif                
default:
InBlock.gif                    level
="-1";
InBlock.gif                    
break;
ExpandedSubBlockEnd.gif            }

InBlock.gif            
if (level=="1"return "YOU ARE AUTHORIZED";
ExpandedSubBlockEnd.gif        }

InBlock.gif        
catch (Exception exc)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return "Authentication failure: " + exc.ToString();
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

InBlock.gif    
return "Authentication failure";
ExpandedBlockEnd.gif}

Points of Interest

TestHash checks to see if the Hash contains a timestamp or an already-hashed token, and calls itself once again in case of failure: if someone is calling the service, let's say, at 11:34:58 the Key is valid from 11:34:00 until 11:35:00, that is during two minutes.

The client side may be implemented in any language: ASP classical, JScript or VBScript, PHP, Python, etc. I have intention to post this code too next time...

转载于:https://www.cnblogs.com/lxinxuan/archive/2007/04/27/729700.html

package com.cissst.servlet; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import com.cissst.entity.Admin; import com.cissst.entity.CustomAccount; import com.cissst.service.IAdminService; import com.cissst.service.ICustomAccountService; import com.cissst.service.impl.AdminServiceImpl; import com.cissst.service.impl.CustomAccountServiceImpl; import com.cissst.util.MD5Util; public class UserServlet extends HttpServlet{ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); String action = request.getParameter("action"); IAdminService as = new AdminServiceImpl(); ICustomAccountService cs = new CustomAccountServiceImpl(); HttpSession session = request.getSession(); if("login".equals(action)){ String name = request.getParameter("username"); String password = MD5Util.encode(request.getParameter("password")); String usertype = request.getParameter("usertype"); Admin a = as.findBynp(name, password); CustomAccount c = cs.findBynp(name, password); if(a != null){ String n = a.getName(); String p = a.getPassword(); if(n.equals(name) && p.equals(password)&&"admin".equals(usertype)){ session.setAttribute("admin", a); response.sendRedirect("index.jsp"); }else{ response.getWriter().write("<script charset='UTF-8'>alert(\"用户名或密码错误!\");" + "location.href='index.jsp';</script>"); } }else if(c != null){ String n = c.getUsername(); String p = c.getPassword(); if(n.equals(name) && p.equals(password)&&"user".equals(usertype)){ session.setAttribute("custom_Account", c); response.sendRedirect("index2.jsp"); }else{ response.getWriter().write("<script charset='UTF-8'>alert(\"用户名或密码错误!\");" + "location.href='index.jsp';</script>"); } }else{ response.getWriter().write("<script charset='UTF-8'>alert(\"用户名或密码错误!\");" + "location.href='index.jsp';</script>"); } }else if("logout".equals(action)){ session.invalidate(); //if (request.getSession(false)==null) System.out.println(123); response.sendRedirect("login.jsp"); }else if("relogin".equals(action)){ session.invalidate(); response.sendRedirect("login.jsp"); } } }
最新发布
08-04
<think>我们正在处理一个基于Servlet的登录系统,涉及管理员(Admin)和普通用户(CustomAccount)的登录验证。以下步骤将详细说明如何实现: 1. 前端提交登录表单(包含用户名、密码和用户类型) 2. 后端Servlet接收请求,获取参数(用户名、密码、用户类型) 3. 对密码进行MD5加密(因为数据库中存储的是MD5加密后的密码) 4. 根据用户类型(管理员或普通用户)调用相应的Service层方法进行验证 5. 验证通过后,将用户信息存入HttpSession 6. 根据用户类型重定向到不同的主页 注意:我们假设已经有一个工具类可以生成MD5加密字符串,并且数据库中有两张表:管理员表(admin)和普通用户表(custom_account)。 具体实现步骤: 1. 创建登录Servlet(例如LoginServlet),并通过@WebServlet注解或web.xml配置映射路径(如"/login")。 2. 在LoginServlet的doPost方法中处理登录请求。 3. 代码结构示例: 注意:以下代码为示例,需要根据实际项目调整(如异常处理、日志记录等)</think>### Java Servlet 登录逻辑实现指南 基于您提供的引用信息和需求,以下是完整的登录逻辑实现方案,涵盖管理员(Admin)和普通用户(CustomAccount)的身份验证、MD5加密、会话管理和页面跳转: #### 核心实现步骤 ```java // LoginServlet.java @WebServlet("/login") public class LoginServlet extends HttpServlet { private UserService userService = new UserServiceImpl(); // 服务层实例 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取请求参数 String username = request.getParameter("username"); String rawPassword = request.getParameter("password"); String userType = request.getParameter("userType"); // admin 或 custom // 2. MD5加密密码 String encryptedPwd = MD5Util.encrypt(rawPassword); // 3. 身份验证 Result result; if ("admin".equals(userType)) { result = userService.loginAdmin(username, encryptedPwd); } else { result = userService.loginCustom(username, encryptedPwd); } // 4. 会话管理与跳转 HttpSession session = request.getSession(); if (result.isSuccess()) { // 存储用户对象到会话 session.setAttribute("currentUser", result.getData()); // 根据用户类型重定向 if ("admin".equals(userType)) { response.sendRedirect("admin_dashboard.jsp"); } else { response.sendRedirect("user_home.jsp"); } } else { // 登录失败处理 session.setAttribute("errorMsg", result.getMessage()); response.sendRedirect("login.jsp"); } } } ``` #### 关键组件实现 1. **MD5加密工具类** (引用[1]) ```java // MD5Util.java public class MD5Util { public static String encrypt(String input) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] digest = md.digest(input.getBytes()); return HexFormat.of().formatHex(digest); // Java 17+ } catch (NoSuchAlgorithmException e) { throw new RuntimeException("MD5加密失败", e); } } } ``` 2. **服务层验证逻辑** (引用[3]) ```java // UserServiceImpl.java public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoImpl(); @Override public Result loginAdmin(String username, String encryptedPwd) { Admin admin = userDao.findAdminByUsername(username); if (admin == null) { return Result.error("管理员账号不存在"); } if (!admin.getPassword().equals(encryptedPwd)) { return Result.error("密码错误"); } return Result.success(admin); } @Override public Result loginCustom(String username, String encryptedPwd) { CustomAccount account = userDao.findCustomByUsername(username); if (account == null) { return Result.error("用户账号不存在"); } if (!account.getPassword().equals(encryptedPwd)) { return Result.error("密码错误"); } return Result.success(account); } } ``` 3. **DAO层数据库操作** (使用优化后的DBUtil) ```java // UserDaoImpl.java public class UserDaoImpl implements UserDao { public Admin findAdminByUsername(String username) { String sql = "SELECT * FROM admin WHERE username = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { Admin admin = new Admin(); admin.setId(rs.getInt("id")); admin.setUsername(rs.getString("username")); admin.setPassword(rs.getString("password")); return admin; } } } catch (SQLException e) { throw new RuntimeException("数据库查询失败", e); } return null; } // CustomAccount查询类似实现... } ``` #### 安全增强措施 1. **密码安全** - 始终使用MD5或更安全的bcrypt加密存储密码 - 添加盐值(salt)增强安全性: ```java public static String encryptWithSalt(String input) { String salt = "随机盐值"; // 应为每个用户唯一 return encrypt(salt + input); } ``` 2. **会话保护** (引用[2]) ```java // 在登录成功后设置 session.setAttribute("userType", userType); session.setMaxInactiveInterval(30 * 60); // 30分钟超时 // 在过滤器中检查 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpSession session = request.getSession(false); if (session == null || session.getAttribute("currentUser") == null) { ((HttpServletResponse) res).sendRedirect("login.jsp"); } else { chain.doFilter(req, res); } } ``` 3. **防SQL注入** - 始终使用`PreparedStatement`代替字符串拼接 - 对用户输入进行合法性验证: ```java if (!username.matches("[a-zA-Z0-9_]{4,20}")) { return Result.error("用户名格式错误"); } ``` #### 架构优化建议 1. **使用MVC框架** (引用[2]) - 推荐Spring Security处理认证授权 - 采用Spring MVC简化控制器: ```java @PostMapping("/login") public String login(@RequestParam String username, @RequestParam String password, Model model) { Authentication auth = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(username, password) ); SecurityContextHolder.getContext().setAuthentication(auth); return "redirect:/dashboard"; } ``` 2. **密码加密升级** - 使用更安全的bcrypt代替MD5: ```java BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); String encrypted = encoder.encode(rawPassword); ``` #### 典型登录流程 ```mermaid sequenceDiagram participant Browser participant Servlet participant Service participant DAO participant DB Browser->>Servlet: POST /login (username, pwd, type) Servlet->>Servlet: MD5加密密码 alt 管理员登录 Servlet->>Service: loginAdmin(username, encryptedPwd) Service->>DAO: findAdminByUsername() DAO->>DB: SELECT * FROM admin WHERE username=? DB-->>DAO: Admin数据 else 普通用户登录 Servlet->>Service: loginCustom(username, encryptedPwd) Service->>DAO: findCustomByUsername() DAO->>DB: SELECT * FROM custom_account WHERE username=? DB-->>DAO: CustomAccount数据 end Service-->>Servlet: 验证结果 alt 验证成功 Servlet->>Servlet: 创建用户会话 Servlet-->>Browser: 重定向到dashboard else 验证失败 Servlet-->>Browser: 重定向到登录页(带错误信息) end ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值