基于SpringBoot + Android实现登录功能

 更新时间:2024年11月26日 08:52:14   作者:繁依Fanyi  
在移动互联网的今天,许多应用需要通过移动端实现与服务器的交互功能,其中登录是最常见且基础的一种功能,本篇博客将详细介绍如何使用 Spring Boot 和 Android 实现一个完整的登录功能,需要的朋友可以参考下

引言

在移动互联网的今天,许多应用需要通过移动端实现与服务器的交互功能,其中登录是最常见且基础的一种功能。通过登录,用户可以获得独特的身份标识,从而访问特定的资源或服务。本篇博客将详细介绍如何使用 Spring Boot 和 Android 实现一个完整的登录功能,从后端 API 的构建到 Android 端的交互,旨在为读者提供一套完整的解决方案。

1. 简单分析

在讨论如何实现登录功能之前,我们需要明确需求。通常情况下,登录功能会包含以下几个需求:

  • 用户登录:用户通过输入用户名(或手机号、邮箱)和密码进行登录。
  • 身份验证:服务器需要验证用户身份是否合法,是否拥有访问权限。
  • Token 授权:为了避免频繁的登录操作,服务器可以返回一个 token,客户端持有该 token 后,能够在一段时间内免除再次登录。
  • 安全性:需要防止常见的攻击手段,如密码泄露、暴力 破解等。

在本项目中,我们将采用基于 JWT(JSON Web Token) 的方式来实现无状态的登录功能,Spring Boot 作为后端框架,Android 作为前端实现登录页面及 Token 管理。

2. 项目环境配置

2.1 后端:Spring Boot 配置

首先,我们需要在后端使用 Spring Boot 作为服务端框架,选择 Spring Security 进行用户身份验证,并使用 JWT 实现无状态的登录管理。

  1. 创建 Spring Boot 项目
    可以通过 Spring Initializr 快速生成项目骨架,选择如下依赖:

    • Spring Web
    • Spring Security
    • Spring Data JPA
    • MySQL(或其他数据库)
    • JWT(通过 Maven 手动引入依赖)
  2. 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 项目配置

在 Android 中,我们可以使用 Retrofit 作为网络请求库,并通过 SharedPreferences 来存储 token 信息。

  • Retrofit 依赖引入
    在 Android 项目的 build.gradle 文件中添加 Retrofit 及其相关依赖:
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
  • 设计用户登录界面登录界面是用户进行身份验证的入口,通常包含用户名(或手机号)、密码输入框,以及登录按钮。

3. Spring Boot 后端开发

在这一部分,我们将重点介绍后端的开发,首先从用户模型的设计开始,然后是 Spring Security 的配置,接着是 JWT 的集成与登录 API 的实现。

3.1 用户模型设计

为了保存用户信息,我们首先需要设计一个用户模型。在这里,我们使用 JPA(Java Persistence API)来定义用户实体,并将其持久化到数据库中。

@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
}

同时,使用 UserRepository 进行数据操作:

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

3.2 Spring Security 配置

Spring Security 是 Spring 框架提供的强大的安全管理模块。在这里,我们需要对 Spring Security 进行配置,使其与 JWT 配合使用,来实现无状态的身份验证。

3.2.1 安全配置类

创建一个 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 保护,因为我们将使用 JWT 进行身份验证。我们也配置了 jwtAuthenticationFilter,它将在每次请求时验证 JWT。

3.3 JWT 的集成

JWT 是一种用于在网络应用之间安全传输信息的紧凑令牌。每个 JWT 都由三部分组成:Header、Payload 和 Signature。下面,我们来实现生成和解析 JWT 的逻辑。

3.3.1 JwtTokenUtil 工具类

创建一个 JwtTokenUtil 工具类,用于生成和验证 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 用于拦截请求并验证 token,确保只有经过身份验证的用户可以访问受保护的资源。

@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 实现

在服务器端,我们需要提供一个登录的 API,用户通过该 API 发送用户名和密码,服务器验证后生成 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 是用户登录时发送的请求对象,包含用户名和密码。而 AuthResponse 是服务器返回的响应对象,包含生成的 JWT。

4. Android 前端开发

接下来,我们将在 Android 中实现登录页面,并与 Spring Boot 后端进行交互。

4.1 使用 Retrofit 进行网络请求

Retrofit 是 Android 平台上广泛使用的网络请求库。首先,我们定义一个接口用于请求登录 API。

public interface ApiService {

    @POST("login")
    Call<AuthResponse> login(@Body AuthRequest authRequest);
}

AuthRequest 类对应后端的登录请求体,AuthResponse 类则用来接收服务器返回的 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 登录页面设计与实现

接下来,我们设计一个简单的登录界面,包括两个 EditText 组件用于输入用户名和密码,外加一个 Button 进行登录操作。

<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 中实现登录逻辑:

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 的存储和管理

为了在后续的请求中使用 JWT,我们可以将其存储在 Android 的 SharedPreferences 中。这样,用户登录后,应用在关闭再打开时依然可以保持登录状态。

            (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() 方法负责发送登录请求并处理服务器的响应。如果登录成功,我们将获取到服务器返回的 JWT 并将其存储在 SharedPreferences 中,以便在后续的请求中使用该 Token 进行身份验证。

4.3 Token 的存储和管理

为了保证用户登录后的身份验证,客户端需要将服务器返回的 JWT 存储起来。SharedPreferences 是 Android 中一种轻量级的数据存储方式,非常适合保存类似于 Token 这样的配置信息。

SharedPreferences sharedPreferences = getSharedPreferences("MyApp", MODE_PRIVATE);
String token = sharedPreferences.getString("JWT_TOKEN", null);

在需要身份验证的请求中,我们可以从 SharedPreferences 中读取保存的 Token,并在请求头中添加该 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();

通过上述代码,所有发送的请求将携带 JWT,服务端能够通过验证 Token 来判断用户是否具有访问权限。

5. 完整登录流程分析

  1. 用户在 Android 客户端输入用户名和密码,点击登录按钮。
  2. 客户端发送 POST 请求到服务器的 /login 接口,请求体中包含用户名和密码。
  3. 服务器验证用户的身份,如果验证成功,则生成 JWT 并返回给客户端。
  4. 客户端接收到 JWT 后,将其存储在 SharedPreferences 中。
  5. 后续请求时,客户端将 JWT 附加在请求头中,服务器根据 JWT 来判断用户是否有权限访问资源。

6. 安全性及优化策略

6.1 HTTPS 加密传输

为了确保数据传输的安全性,建议在实际项目中使用 HTTPS 进行加密传输,避免用户的敏感信息(如密码)被窃取。

6.2 密码加密存储

在服务器端,用户的密码不应该以明文形式存储。通常,我们会使用 BCrypt 等加密算法对用户密码进行加密后再存储到数据库中。

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

6.3 Token 的过期管理

JWT 通常会设置一个过期时间,以确保 Token 不会被长期滥用。客户端在检测到 Token 过期时,应提示用户重新登录。

6.4 防止暴力 破解

为了防止恶意用户通过暴力 破解获取用户密码,建议在登录接口上增加防护机制,如使用验证码,或在多次登录失败后暂时锁定用户账号。

7. 总结

本篇博客介绍了如何使用 Spring Boot 和 Android 实现一个完整的登录功能。从用户模型的设计、Spring Security 的配置、JWT 的集成,到 Android 客户端的登录页面实现、网络请求和 Token 管理,涵盖了从后端到前端的所有关键步骤。登录功能虽然看似简单,但其背后涉及的安全性和可扩展性都是我们需要重点关注的。

以上就是基于SpringBoot + Android实现登录功能的详细内容,更多关于SpringBoot Android实现登录的资料请关注脚本之家其它相关文章!

相关文章

  • 不调用方法实现hutool导出excel图片示例详解

    不调用方法实现hutool导出excel图片示例详解

    这篇文章主要为大家介绍了不调用方法实现hutool导出excel图片示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Java日常练习题,每天进步一点点(53)

    Java日常练习题,每天进步一点点(53)

    下面小编就为大家带来一篇Java基础的几道练习题(分享)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧,希望可以帮到你
    2021-08-08
  • Spring Boot利用@Async异步调用:使用Future及定义超时详解

    Spring Boot利用@Async异步调用:使用Future及定义超时详解

    这篇文章主要给大家介绍了关于Spring Boot利用@Async异步调用:使用Future及定义超时的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用spring boot具有一定的参考学习价值,需要的朋友可以参考下
    2018-05-05
  • Java的动态代理模式之JDK代理详解

    Java的动态代理模式之JDK代理详解

    这篇文章主要介绍了Java的动态代理模式之JDK代理详解,代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理,JDK 实现代理只需要使用 newProxyInstance 方法,但是该方法需要接收三个参数,需要的朋友可以参考下
    2023-11-11
  • Java中常用的类型转换(推荐)

    Java中常用的类型转换(推荐)

    这篇文章主要介绍了Java中常用的类型转换(推荐)的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-06-06
  • Spring如何正确注入集合类型

    Spring如何正确注入集合类型

    这篇文章主要介绍了Spring如何正确注入集合类型,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • SpringBoot + 微信公众号JSAPI支付功能的实现

    SpringBoot + 微信公众号JSAPI支付功能的实现

    这篇文章主要介绍了SpringBoot + 微信公众号JSAPI支付功能的实现,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • SpringBoot2.3整合redis缓存自定义序列化的实现

    SpringBoot2.3整合redis缓存自定义序列化的实现

    这篇文章主要介绍了SpringBoot2.3整合redis缓存自定义序列化的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • idea在用Mybatis时xml文件sql不提示解决办法(提示后背景颜色去除)

    idea在用Mybatis时xml文件sql不提示解决办法(提示后背景颜色去除)

    mybatis的xml文件配置的时候,有时候会没有提示,这让我们很头疼,下面这篇文章主要给大家介绍了关于idea在用Mybatis时xml文件sql不提示的解决办法,提示后背景颜色去除的相关资料,需要的朋友可以参考下
    2023-03-03
  • Spring Boot中是如何处理日期时间格式的

    Spring Boot中是如何处理日期时间格式的

    这篇文章主要介绍了Spring Boot中是如何处理日期时间格式的,帮助大家更好的理解和学习spring boot框架,感兴趣的朋友可以了解下
    2020-11-11

最新评论