Eliminate Messy Login Logic: Unified Multi‑Channel Authentication with Spring Boot Factory & Strategy Patterns
This article demonstrates how to replace tangled if‑else login code with a clean Spring Boot solution that combines the Factory and Strategy patterns, enabling easy addition of multiple authentication methods such as password, WeChat, SMS, and future providers while improving extensibility, readability, and testability.
Requirement Analysis: Supporting Multiple Login Methods
The product requests support for ten login methods. The author starts with a typical scenario that includes three methods: username/password, WeChat QR‑code, and SMS verification.
Username/password login : needs password encryption verification and account lock checking.
WeChat QR‑code login : requires calling the WeChat Open Platform API and validating the auth code.
SMS verification login : requires generating a code, checking its validity period and correctness.
Traditional implementation uses a single LoginService with nested if‑else statements to distinguish login types, leading to poor extensibility, tangled responsibilities, and difficulty reusing common logic.
public String login(String loginType, Map<String, Object> params) {
if ("password".equals(loginType)) {
// username/password logic
} else if ("wechat".equals(loginType)) {
// wechat logic
} else if ("sms".equals(loginType)) {
// sms logic
} else {
throw new IllegalArgumentException("Unsupported login type");
}
}Design Pattern Selection
The author chooses the Strategy pattern to decouple each login algorithm and the Factory pattern to create the appropriate strategy instance, keeping the caller unaware of concrete implementations.
Spring Boot Project Setup
Project structure:
src/main/java/com/example/login
├── config
│ └── StrategyConfig.java // strategy bean configuration
├── controller
│ └── LoginController.java // login controller
├── factory
│ └── LoginStrategyFactory.java // factory for strategies
├── model
│ └── LoginRequest.java // request DTO
├── service
│ ├── impl
│ │ ├── PasswordLoginStrategy.java // password strategy
│ │ ├── WechatLoginStrategy.java // wechat strategy
│ │ └── SmsLoginStrategy.java // sms strategy
│ └── LoginStrategy.java // strategy interface
└── Application.javaUnified Login Strategy Interface
public interface LoginStrategy {
// login type identifier, e.g., "password", "wechat"
String getLoginType();
// login method, parameters passed via a Map
String execute(Map<String, Object> params);
}Strategy Implementations
1. Username/Password Strategy
@Service
public class PasswordLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() { return "password"; }
@Override
public String execute(Map<String, Object> params) {
String username = (String) params.get("username");
String password = (String) params.get("password");
// simulate password check
if (!"123456".equals(password)) {
throw new IllegalArgumentException("密码错误");
}
checkUserLocked(username);
return "登录成功(用户名密码)";
}
private void checkUserLocked(String username) {
System.out.println("检查用户" + username + "是否锁定");
}
}2. WeChat QR‑Code Strategy
@Service
public class WechatLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() { return "wechat"; }
@Override
public String execute(Map<String, Object> params) {
String authCode = (String) params.get("authCode");
String openId = callWechatApi(authCode);
String userId = getUserIdByOpenId(openId);
if (userId == null) {
throw new IllegalArgumentException("微信账号未绑定系统用户");
}
return "登录成功(微信扫码)";
}
private String callWechatApi(String authCode) {
System.out.println("调用微信接口,authCode=" + authCode);
return "wechat_open_id_123";
}
}3. SMS Verification Strategy
@Service
public class SmsLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() { return "sms"; }
@Override
public String execute(Map<String, Object> params) {
String phone = (String) params.get("phone");
String code = (String) params.get("code");
if (!"666888".equals(code)) {
throw new IllegalArgumentException("验证码错误");
}
checkPhoneRegistered(phone);
return "登录成功(手机号验证码)";
}
private void checkPhoneRegistered(String phone) {
System.out.println("检查手机号" + phone + "是否注册");
}
}Factory Implementation
@Component
public class LoginStrategyFactory {
private final Map<String, LoginStrategy> strategyMap;
public LoginStrategyFactory(Map<String, LoginStrategy> strategyMap) {
this.strategyMap = new HashMap<>();
strategyMap.forEach((beanName, strategy) ->
this.strategyMap.put(strategy.getLoginType(), strategy)
);
}
public LoginStrategy getStrategy(String loginType) {
LoginStrategy strategy = strategyMap.get(loginType);
if (strategy == null) {
throw new IllegalArgumentException("不支持的登录类型:" + loginType);
}
return strategy;
}
}Controller Integration
@RestController
@RequestMapping("/login")
public class LoginController {
private final LoginStrategyFactory factory;
@Autowired
public LoginController(LoginStrategyFactory factory) {
this.factory = factory;
}
@PostMapping
public String login(@RequestBody LoginRequest request) {
String loginType = request.getLoginType();
Map<String, Object> params = request.getParams();
LoginStrategy strategy = factory.getStrategy(loginType);
return strategy.execute(params);
}
}Testing the Three Login Types
Username/Password Request
{
"loginType": "password",
"params": {
"username": "user123",
"password": "123456"
}
}WeChat QR‑Code Request
{
"loginType": "wechat",
"params": {
"authCode": "wechat_auth_code_456"
}
}Adding a New Method (Alipay)
To support a new provider, only a new strategy class is needed; existing code remains untouched.
@Service("alipayStrategy")
public class AlipayLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() { return "alipay"; }
@Override
public String execute(Map<String, Object> params) {
return "登录成功(支付宝)";
}
}Core Advantages
Strategy pattern : decouples each login algorithm, improving readability.
Factory pattern : encapsulates object creation, adhering to the Open‑Closed Principle.
Spring Boot integration : automatic bean registration via @Service and injection of Map<String, LoginStrategy> simplifies management.
Type safety : the factory validates login types at runtime, preventing NullPointerException.
Best Practices
Validate loginType and params in the controller before delegating to strategies.
Extract common logic (e.g., token generation) into an abstract base strategy if multiple strategies share it.
Add logging and unified exception handling inside each strategy to produce friendly error messages.
Store supported login types in configuration files to avoid hard‑coding values in the front‑end.
Conclusion
By replacing the monolithic if‑else login service with a combination of Factory and Strategy patterns, the login module becomes modular, extensible, and easy to test. New authentication methods can be added as plug‑in strategies without touching existing code, keeping the codebase elegant in the face of changing requirements.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
