SprintBoot 实现基于Token的登录验证功能

SpringBoot 实现基于Token的登录验证功能

一、知识储备

1、基于服务器的验证

我们都知道HTTP协议是无状态的,这种无状态意味着程序需要验证每一次请求,从而辨别客户端的身份。在这之前,程序都是通过在服务端存储的登录信息来辨别请求的。这种方式一般都是通过存储Session来完成。

基于服务器验证方式所暴露的一些问题

  • Session:每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。当越来越多的用户发请求时,内存的开销也会不断增加。
  • 可扩展性:在服务端的内存中使用Session存储登录信息,伴随而来的是可扩展性问题。
  • CORS(跨域资源共享):当我们需要让数据跨多台设备上使用时,跨域资源的共享会是一个让人头疼的问题。在使用AJAX抓取另一个域的资源,就可能会出现禁止请求的情况。
  • CSRF(跨站请求伪造):用户在访问银行网站时,他们很容易收到跨站请求伪造的攻击,并且能够被利用其访问其他的网站。

在这些问题中,可扩展性是最突出的。因此我们有必要去寻求一种更为行之有效的方法。

2、基于Token的验证原理

基于Token的身份验证是无状态的,我们不将用户信息存在服务器中。这种概念解决了在服务端存储信息时的许多问题。NoSession 意味着我们的程序可以根据需要去增减机器,而不用去担心用户是否登录。

3、基于Token的验证过程

过程如下:

  1. 用户通过用户名和密码发送请求
  2. 服务器端程序进行验证
  3. 服务器端程序返回一个带签名的token返回给客户端
  4. 客户端存储token,并且每次访问API都携带Token到服务器端
  5. 服务端验证token,效验成功则返回请求数据,效验失败则返回错误码

4、Token验证的优势

  • 无状态、可扩展

    在客户端存储的Token是无状态的,并且能够被扩展。基于这种无状态和不存储Session信息,负载均衡器能够将用户信息从一个服务器传到其他服务器上。

  • 安全性

    请求中发送token而不再是发送cookie能够防止CSRF(跨站请求伪造)。即使在客户端使用cookie存储tokencookie也仅仅是一个存储机制而不是用于认证。不将信息存储在Session中,让我们少了对Session的操作。

    token是有时效的,一段时间之后用户需要重新验证。

  • 可扩展性

    tokens能够创建与其他程序共享权限的程序

  • 多平台跨域

二、代码实战

1、Model层-User

public class User {

    private int id;
    private String username;
    private String password;
    private String token;
}

2、mapper层

UserMapper

@Mapper
public interface UserMapper {

    User getByUsernamePassword(String username,String password);

    User getByToken(String token);

    int update(User user);
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.token.mapper.UserMapper">

    <select id="getByUsernamePassword" resultType="com.example.token.model.User">
        select * from user where username = #{username} and password = #{password}
    </select>

    <select id="getByToken" resultType="com.example.token.model.User">
        select * from user where token = #{token}
    </select>

    <update id="update" parameterType="com.example.token.model.User">
        update user
        <set>
            token = #{token,jdbcType=VARCHAR}
        </set>
        where id = #{id,jdbcType=BIGINT}
    </update>

</mapper>

3、Util工具类

public class Util {

    public static String getUUID(){
        return UUID.randomUUID().toString().replace("-","");
    }
}

Utile工具类的getUUID方法用户随机生成Token

4、Service层-LoginService

@Service
public class LoginService {

    @Autowired
    private UserMapper userMapper;

    public String login(String username,String password){
        User user = userMapper.getByUsernamePassword(username, password);
        if (user!=null){
            String token = Util.getUUID();
            user.setToken(token);

            userMapper.update(user);

            return token;
        }

        return "";
    }

    public Boolean isLogin(String token){
        User user = userMapper.getByToken(token);
        if (user!=null){
            return true;
        }
        return false;
    }
}

5、Controller层-LoginController

@Controller
public class LoginController {
    
    @Autowired
    private LoginService loginService;

    @GetMapping("/admin")
    public String admin(HttpServletRequest request){
        Cookie[] cookies = request.getCookies();

        Boolean isLogin = false;

        for (int i = 0; i < cookies.length; i++) {
            if (cookies[i].getName().equals("login_token")){//有储存token的cookie
                isLogin = loginService.isLogin(cookies[i].getValue());
                break;
            }
        }

        //如果token存在且有效
        if (isLogin == true){
            return "dashboard";
        }else {//返回登录页面
            return "index";
        }
    }

    @PostMapping("/admin/login")
    public String login(String username,String password,HttpServletResponse response){

        String token = loginService.login(username, password);
        if (token!=""){//登录成功,并生成了token
            Cookie cookie = new Cookie("login_token", token);
            cookie.setMaxAge(3 * 24 * 60 * 60);//有效期3天
            cookie.setPath("/admin");//必须要设置
            cookie.setDomain("");
            cookie.setHttpOnly(false);

            //将cookie对象加入response响应
            response.addCookie(cookie);

            return "dashboard";
        }

        //登录失败
        return "error";
    }
}

三、Token验证思路总结

  • Token的生成可以用UUID类随机生成,或者也可以用JWS(JSON Web Token)
  • 用户登录后台网页时,输入的网址应该是http://localhost:8080/admin
  • Controller层收到/admin请求时,应该判断请求是否携带token
    • 若携带了token,则调用LoginService方法将携带的token与数据库中token比对。若比对成功,则直接进入后台页面dashboard.html
    • 若未携带token或者token比对失败,则返回后台登录页面index.html
  • 用户在后台登录页面index.html输入用户名username和密码password后,发起/admin/login请求
  • Controller层接收到请求后,比对数据库中的usernamepassword,如果比对成功,则随机生成token,并将token放入Cookie中,连同其它响应信息一并返回给客户端浏览器
  • 浏览器保存存放token的Cookie,下一次输入http://localhost:8080/admin网址时,则经过token验证后不需要走后台登录页面,直接进入后台主页
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用Spring Security和JWT(JSON Web Token)来实现基于token登录验证接口。下面是一个简单的示例: 1. 添加依赖 在pom.xml中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> ``` 2. 配置Spring Security 在Spring Boot的配置类中添加以下代码: ``` @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsServiceImpl userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**").permitAll() .anyRequest().authenticated() .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean public JwtAuthenticationFilter authenticationJwtTokenFilter() { return new JwtAuthenticationFilter(); } } ``` 这个配置类禁用了CSRF保护,允许所有用户访问“/api/auth/**”接口,其余接口需要进行认证。它还使用“JwtAuthenticationFilter”类来验证token。 3. 实现UserDetailsService 创建一个UserDetailsService的实现类,用于从数据库或其他数据源中获取用户信息。例如: ``` @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override @Transactional public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username)); return UserDetailsImpl.build(user); } } ``` 这里的“UserDetailsImpl”类是一个自定义的实现了UserDetails接口的类,用于封装用户信息。 4. 实现JwtUtils 创建一个JwtUtils类,用于生成和验证token。例如: ``` @Component public class JwtUtils { @Value("${jwt.secret}") private String jwtSecret; @Value("${jwt.expirationMs}") private int jwtExpirationMs; public String generateJwtToken(Authentication authentication) { UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal(); return Jwts.builder() .setSubject((userPrincipal.getUsername())) .setIssuedAt(new Date()) .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)) .signWith(SignatureAlgorithm.HS512, jwtSecret) .compact(); } public boolean validateJwtToken(String authToken) { try { Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken); return true; } catch (SignatureException e) { logger.error("Invalid JWT signature: {}", e.getMessage()); } catch (MalformedJwtException e) { logger.error("Invalid JWT token: {}", e.getMessage()); } catch (ExpiredJwtException e) { logger.error("JWT token is expired: {}", e.getMessage()); } catch (UnsupportedJwtException e) { logger.error("JWT token is unsupported: {}", e.getMessage()); } catch (IllegalArgumentException e) { logger.error("JWT claims string is empty: {}", e.getMessage()); } return false; } public String getUsernameFromJwtToken(String token) { return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject(); } } ``` 这里的“jwt.secret”和“jwt.expirationMs”分别是用于生成token的密钥和过期时间(以毫秒为单位)。 5. 实现JwtAuthenticationFilter 创建一个JwtAuthenticationFilter类,用于在每个请求上验证token。例如: ``` public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtUtils jwtUtils; @Autowired private UserDetailsServiceImpl userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String jwt = parseJwt(request); if (jwt != null && jwtUtils.validateJwtToken(jwt)) { String username = jwtUtils.getUsernameFromJwtToken(jwt); UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception e) { logger.error("Cannot set user authentication: {}", e.getMessage()); } filterChain.doFilter(request, response); } private String parseJwt(HttpServletRequest request) { String headerAuth = request.getHeader("Authorization"); if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) { return headerAuth.substring(7, headerAuth.length()); } return null; } } ``` 这个过滤器将从请求头中获取token,并使用JwtUtils类来验证token。如果token有效,则将用户信息设置为Spring Security的上下文中的认证信息。 6. 创建登录接口 创建一个登录接口,用于验证用户并生成token。例如: ``` @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtUtils jwtUtils; @PostMapping("/signin") public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); SecurityContextHolder.getContext().setAuthentication(authentication); String jwt = jwtUtils.generateJwtToken(authentication); return ResponseEntity.ok(new JwtResponse(jwt)); } } ``` 这个接口将使用AuthenticationManager来验证用户信息,并使用JwtUtils类来生成token。如果验证成功,则返回一个包含token的JwtResponse对象。 7. 测试接口 现在可以测试接口了。使用POST方法向“/api/auth/signin”发送以下JSON数据: ``` { "username": "your_username", "password": "your_password" } ``` 如果验证成功,则会返回一个包含token的JSON响应。以后,可以在每个请求中将token作为“Authorization”请求头的值发送。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值