基于SpringBoot + Android實(shí)現(xiàn)登錄功能
引言
在移動(dòng)互聯(lián)網(wǎng)的今天,許多應(yīng)用需要通過移動(dòng)端實(shí)現(xiàn)與服務(wù)器的交互功能,其中登錄是最常見且基礎(chǔ)的一種功能。通過登錄,用戶可以獲得獨(dú)特的身份標(biāo)識(shí),從而訪問特定的資源或服務(wù)。本篇博客將詳細(xì)介紹如何使用 Spring Boot 和 Android 實(shí)現(xiàn)一個(gè)完整的登錄功能,從后端 API 的構(gòu)建到 Android 端的交互,旨在為讀者提供一套完整的解決方案。
1. 簡(jiǎn)單分析
在討論如何實(shí)現(xiàn)登錄功能之前,我們需要明確需求。通常情況下,登錄功能會(huì)包含以下幾個(gè)需求:
- 用戶登錄:用戶通過輸入用戶名(或手機(jī)號(hào)、郵箱)和密碼進(jìn)行登錄。
- 身份驗(yàn)證:服務(wù)器需要驗(yàn)證用戶身份是否合法,是否擁有訪問權(quán)限。
- Token 授權(quán):為了避免頻繁的登錄操作,服務(wù)器可以返回一個(gè) token,客戶端持有該 token 后,能夠在一段時(shí)間內(nèi)免除再次登錄。
- 安全性:需要防止常見的攻擊手段,如密碼泄露、暴力 破解等。
在本項(xiàng)目中,我們將采用基于 JWT(JSON Web Token) 的方式來實(shí)現(xiàn)無狀態(tài)的登錄功能,Spring Boot 作為后端框架,Android 作為前端實(shí)現(xiàn)登錄頁面及 Token 管理。
2. 項(xiàng)目環(huán)境配置
2.1 后端:Spring Boot 配置
首先,我們需要在后端使用 Spring Boot 作為服務(wù)端框架,選擇 Spring Security 進(jìn)行用戶身份驗(yàn)證,并使用 JWT 實(shí)現(xiàn)無狀態(tài)的登錄管理。
創(chuàng)建 Spring Boot 項(xiàng)目
可以通過 Spring Initializr 快速生成項(xiàng)目骨架,選擇如下依賴:- Spring Web
- Spring Security
- Spring Data JPA
- MySQL(或其他數(shù)據(jù)庫)
- JWT(通過 Maven 手動(dòng)引入依賴)
JWT 依賴引入
在pom.xml
文件中添加 JWT 的依賴:
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.2</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.2</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.2</version> </dependency>
2.2 前端:Android 項(xiàng)目配置
在 Android 中,我們可以使用 Retrofit 作為網(wǎng)絡(luò)請(qǐng)求庫,并通過 SharedPreferences
來存儲(chǔ) token 信息。
- Retrofit 依賴引入
在 Android 項(xiàng)目的build.gradle
文件中添加 Retrofit 及其相關(guān)依賴:
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
設(shè)計(jì)用戶登錄界面登錄界面是用戶進(jìn)行身份驗(yàn)證的入口,通常包含用戶名(或手機(jī)號(hào))、密碼輸入框,以及登錄按鈕。
3. Spring Boot 后端開發(fā)
在這一部分,我們將重點(diǎn)介紹后端的開發(fā),首先從用戶模型的設(shè)計(jì)開始,然后是 Spring Security 的配置,接著是 JWT 的集成與登錄 API 的實(shí)現(xiàn)。
3.1 用戶模型設(shè)計(jì)
為了保存用戶信息,我們首先需要設(shè)計(jì)一個(gè)用戶模型。在這里,我們使用 JPA(Java Persistence API)來定義用戶實(shí)體,并將其持久化到數(shù)據(jù)庫中。
@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; private String email; // other fields, getters and setters }
同時(shí),使用 UserRepository
進(jìn)行數(shù)據(jù)操作:
@Repository public interface UserRepository extends JpaRepository<User, Long> { Optional<User> findByUsername(String username); }
3.2 Spring Security 配置
Spring Security 是 Spring 框架提供的強(qiáng)大的安全管理模塊。在這里,我們需要對(duì) Spring Security 進(jìn)行配置,使其與 JWT 配合使用,來實(shí)現(xiàn)無狀態(tài)的身份驗(yàn)證。
3.2.1 安全配置類
創(chuàng)建一個(gè) SecurityConfig
類,用于配置 Spring Security:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
這里我們禁用了 CSRF 保護(hù),因?yàn)槲覀儗⑹褂?JWT 進(jìn)行身份驗(yàn)證。我們也配置了 jwtAuthenticationFilter
,它將在每次請(qǐng)求時(shí)驗(yàn)證 JWT。
3.3 JWT 的集成
JWT 是一種用于在網(wǎng)絡(luò)應(yīng)用之間安全傳輸信息的緊湊令牌。每個(gè) JWT 都由三部分組成:Header、Payload 和 Signature。下面,我們來實(shí)現(xiàn)生成和解析 JWT 的邏輯。
3.3.1 JwtTokenUtil 工具類
創(chuàng)建一個(gè) JwtTokenUtil
工具類,用于生成和驗(yàn)證 JWT。
@Component public class JwtTokenUtil { private static final String SECRET_KEY = "your_secret_key"; public String generateToken(UserDetails userDetails) { return Jwts.builder() .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public String extractUsername(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody() .getSubject(); } public boolean validateToken(String token, UserDetails userDetails) { final String username = extractUsername(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } private boolean isTokenExpired(String token) { final Date expiration = Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody() .getExpiration(); return expiration.before(new Date()); } }
3.3.2 JwtAuthenticationFilter
JwtAuthenticationFilter
用于攔截請(qǐng)求并驗(yàn)證 token,確保只有經(jīng)過身份驗(yàn)證的用戶可以訪問受保護(hù)的資源。
@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String authorizationHeader = request.getHeader("Authorization"); String username = null; String jwt = null; if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { jwt = authorizationHeader.substring(7); username = jwtTokenUtil.extractUsername(jwt); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(jwt, userDetails)) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } filterChain.doFilter(request, response); } }
3.4 登錄 API 實(shí)現(xiàn)
在服務(wù)器端,我們需要提供一個(gè)登錄的 API,用戶通過該 API 發(fā)送用戶名和密碼,服務(wù)器驗(yàn)證后生成 JWT 返回給客戶端。
@RestController public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private UserDetailsService userDetailsService; @PostMapping("/login") public ResponseEntity<?> createAuthenticationToken (@RequestBody AuthRequest authRequest) throws Exception { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()) ); } catch (BadCredentialsException e) { throw new Exception("Incorrect username or password", e); } final UserDetails userDetails = userDetailsService .loadUserByUsername(authRequest.getUsername()); final String jwt = jwtTokenUtil.generateToken(userDetails); return ResponseEntity.ok(new AuthResponse(jwt)); } }
這里,AuthRequest 是用戶登錄時(shí)發(fā)送的請(qǐng)求對(duì)象,包含用戶名和密碼。而 AuthResponse 是服務(wù)器返回的響應(yīng)對(duì)象,包含生成的 JWT。
4. Android 前端開發(fā)
接下來,我們將在 Android 中實(shí)現(xiàn)登錄頁面,并與 Spring Boot 后端進(jìn)行交互。
4.1 使用 Retrofit 進(jìn)行網(wǎng)絡(luò)請(qǐng)求
Retrofit 是 Android 平臺(tái)上廣泛使用的網(wǎng)絡(luò)請(qǐng)求庫。首先,我們定義一個(gè)接口用于請(qǐng)求登錄 API。
public interface ApiService { @POST("login") Call<AuthResponse> login(@Body AuthRequest authRequest); }
AuthRequest
類對(duì)應(yīng)后端的登錄請(qǐng)求體,AuthResponse
類則用來接收服務(wù)器返回的 JWT。
public class AuthRequest { private String username; private String password; // Constructor, getters and setters } public class AuthResponse { private String jwt; // Constructor, getters and setters }
4.2 登錄頁面設(shè)計(jì)與實(shí)現(xiàn)
接下來,我們?cè)O(shè)計(jì)一個(gè)簡(jiǎn)單的登錄界面,包括兩個(gè) EditText 組件用于輸入用戶名和密碼,外加一個(gè) Button 進(jìn)行登錄操作。
<EditText android:id="@+id/etUsername" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Username" /> <EditText android:id="@+id/etPassword" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Password" android:inputType="textPassword" /> <Button android:id="@+id/btnLogin" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Login" />
在 MainActivity.java
中實(shí)現(xiàn)登錄邏輯:
public class MainActivity extends AppCompatActivity { private EditText etUsername; private EditText etPassword; private Button btnLogin; private ApiService apiService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); etUsername = findViewById(R.id.etUsername); etPassword = findViewById(R.id.etPassword); btnLogin = findViewById(R.id.btnLogin); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://your-server-url/") .addConverterFactory(GsonConverterFactory.create()) .build(); apiService = retrofit.create(ApiService.class); btnLogin.setOnClickListener(v -> login()); } private void login() { String username = etUsername.getText().toString(); String password = etPassword.getText().toString(); AuthRequest authRequest = new AuthRequest(username, password); Call<AuthResponse> call = apiService.login(authRequest); call.enqueue(new Callback<AuthResponse>() { @Override public void onResponse(Call<AuthResponse> call, Response<AuthResponse> response) { if (response.isSuccessful()) { String jwt = response.body().getJwt(); // Store JWT in SharedPreferences SharedPreferences preferences = getSharedPreferences("my_prefs", MODE_PRIVATE); preferences.edit().putString("jwt", jwt).apply(); // Navigate to another activity } else { // Handle failure } } @Override public void onFailure(Call<AuthResponse> call, Throwable t) { // Handle network failure } }); } }
4.3 Token 的存儲(chǔ)和管理
為了在后續(xù)的請(qǐng)求中使用 JWT,我們可以將其存儲(chǔ)在 Android 的 SharedPreferences
中。這樣,用戶登錄后,應(yīng)用在關(guān)閉再打開時(shí)依然可以保持登錄狀態(tài)。
(Call<AuthResponse> call, Response<AuthResponse> response) { if (response.isSuccessful()) { AuthResponse authResponse = response.body(); String token = authResponse.getJwt(); // Store the token using SharedPreferences SharedPreferences sharedPreferences = getSharedPreferences("MyApp", MODE_PRIVATE); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString("JWT_TOKEN", token); editor.apply(); Toast.makeText(MainActivity.this, "Login successful", Toast.LENGTH_SHORT).show(); // Navigate to another activity after successful login Intent intent = new Intent(MainActivity.this, DashboardActivity.class); startActivity(intent); finish(); } else { Toast.makeText(MainActivity.this, "Login failed", Toast.LENGTH_SHORT).show(); } } @Override public void onFailure(Call<AuthResponse> call, Throwable t) { Toast.makeText(MainActivity.this, "Network error", Toast.LENGTH_SHORT).show(); } }); } }
在上面的代碼中,login()
方法負(fù)責(zé)發(fā)送登錄請(qǐng)求并處理服務(wù)器的響應(yīng)。如果登錄成功,我們將獲取到服務(wù)器返回的 JWT 并將其存儲(chǔ)在 SharedPreferences
中,以便在后續(xù)的請(qǐng)求中使用該 Token 進(jìn)行身份驗(yàn)證。
4.3 Token 的存儲(chǔ)和管理
為了保證用戶登錄后的身份驗(yàn)證,客戶端需要將服務(wù)器返回的 JWT 存儲(chǔ)起來。SharedPreferences
是 Android 中一種輕量級(jí)的數(shù)據(jù)存儲(chǔ)方式,非常適合保存類似于 Token 這樣的配置信息。
SharedPreferences sharedPreferences = getSharedPreferences("MyApp", MODE_PRIVATE); String token = sharedPreferences.getString("JWT_TOKEN", null);
在需要身份驗(yàn)證的請(qǐng)求中,我們可以從 SharedPreferences
中讀取保存的 Token,并在請(qǐng)求頭中添加該 Token。
OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(chain -> { Request originalRequest = chain.request(); String token = sharedPreferences.getString("JWT_TOKEN", null); if (token != null) { Request newRequest = originalRequest.newBuilder() .header("Authorization", "Bearer " + token) .build(); return chain.proceed(newRequest); } return chain.proceed(originalRequest); }) .build();
通過上述代碼,所有發(fā)送的請(qǐng)求將攜帶 JWT,服務(wù)端能夠通過驗(yàn)證 Token 來判斷用戶是否具有訪問權(quán)限。
5. 完整登錄流程分析
- 用戶在 Android 客戶端輸入用戶名和密碼,點(diǎn)擊登錄按鈕。
- 客戶端發(fā)送 POST 請(qǐng)求到服務(wù)器的
/login
接口,請(qǐng)求體中包含用戶名和密碼。 - 服務(wù)器驗(yàn)證用戶的身份,如果驗(yàn)證成功,則生成 JWT 并返回給客戶端。
- 客戶端接收到 JWT 后,將其存儲(chǔ)在
SharedPreferences
中。 - 后續(xù)請(qǐng)求時(shí),客戶端將 JWT 附加在請(qǐng)求頭中,服務(wù)器根據(jù) JWT 來判斷用戶是否有權(quán)限訪問資源。
6. 安全性及優(yōu)化策略
6.1 HTTPS 加密傳輸
為了確保數(shù)據(jù)傳輸?shù)陌踩?,建議在實(shí)際項(xiàng)目中使用 HTTPS 進(jìn)行加密傳輸,避免用戶的敏感信息(如密碼)被竊取。
6.2 密碼加密存儲(chǔ)
在服務(wù)器端,用戶的密碼不應(yīng)該以明文形式存儲(chǔ)。通常,我們會(huì)使用 BCrypt
等加密算法對(duì)用戶密碼進(jìn)行加密后再存儲(chǔ)到數(shù)據(jù)庫中。
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
6.3 Token 的過期管理
JWT 通常會(huì)設(shè)置一個(gè)過期時(shí)間,以確保 Token 不會(huì)被長(zhǎng)期濫用??蛻舳嗽跈z測(cè)到 Token 過期時(shí),應(yīng)提示用戶重新登錄。
6.4 防止暴力 破解
為了防止惡意用戶通過暴力 破解獲取用戶密碼,建議在登錄接口上增加防護(hù)機(jī)制,如使用驗(yàn)證碼,或在多次登錄失敗后暫時(shí)鎖定用戶賬號(hào)。
7. 總結(jié)
本篇博客介紹了如何使用 Spring Boot 和 Android 實(shí)現(xiàn)一個(gè)完整的登錄功能。從用戶模型的設(shè)計(jì)、Spring Security 的配置、JWT 的集成,到 Android 客戶端的登錄頁面實(shí)現(xiàn)、網(wǎng)絡(luò)請(qǐng)求和 Token 管理,涵蓋了從后端到前端的所有關(guān)鍵步驟。登錄功能雖然看似簡(jiǎn)單,但其背后涉及的安全性和可擴(kuò)展性都是我們需要重點(diǎn)關(guān)注的。
以上就是基于SpringBoot + Android實(shí)現(xiàn)登錄功能的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Android實(shí)現(xiàn)登錄的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot中AOP的動(dòng)態(tài)匹配和靜態(tài)匹配詳解
這篇文章主要介紹了SpringBoot中AOP的動(dòng)態(tài)匹配和靜態(tài)匹配詳解,在創(chuàng)建代理的時(shí)候?qū)δ繕?biāo)類的每個(gè)連接點(diǎn)使用靜態(tài)切點(diǎn)檢查,如果僅通過靜態(tài)切點(diǎn)檢查就可以知道連接點(diǎn)是不匹配的,則在運(yùn)行時(shí)就不再進(jìn)行動(dòng)態(tài)檢查了,需要的朋友可以參考下2023-09-09使用eclipse 實(shí)現(xiàn)將springboot項(xiàng)目打成jar包
這篇文章主要介紹了使用eclipse 實(shí)現(xiàn)將springboot項(xiàng)目打成jar包的流程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07詳解Java中格式化日期的DateFormat與SimpleDateFormat類
DateFormat其本身是一個(gè)抽象類,SimpleDateFormat 類是DateFormat類的子類,一般情況下來講DateFormat類很少會(huì)直接使用,而都使用SimpleDateFormat類完成,下面我們具體來看一下兩個(gè)類的用法:2016-05-05沒有編輯器的環(huán)境下是如何創(chuàng)建Servlet(Tomcat+Java)項(xiàng)目的?
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識(shí),文章圍繞著在沒有編輯器的環(huán)境下如何創(chuàng)建Servlet(Tomcat+Java)項(xiàng)目展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06