单点登录前端实现代码
一、引言
单点登录(Single Sign-On,SSO)是一种身份验证机制,允许用户使用一组凭证(通常是用户名和密码)登录到多个相关的应用程序或系统,而无需在每个应用程序中分别输入凭证,单点登录的目的是提高用户体验,减少用户的记忆负担,并提高安全性。
我们将介绍如何使用 Java 实现单点登录,并提供相应的前端代码示例,我们将使用 Spring Security 框架来实现单点登录,并使用 JWT(JSON Web Token)来管理用户的会话。
二、单点登录实现方案
1、用户认证:用户首先需要在身份验证服务中进行认证,身份验证服务将验证用户的凭证,并生成一个 JWT 令牌。
2、令牌存储:生成的 JWT 令牌将被存储在用户的浏览器中,令牌将包含用户的身份信息和会话信息。
3、单点登录:当用户访问其他需要单点登录的应用程序时,应用程序将首先检查用户的浏览器中是否存在有效的 JWT 令牌,如果令牌存在且有效,应用程序将使用令牌中的身份信息来验证用户的身份,并允许用户访问应用程序。
4、令牌刷新:为了保持用户的会话活跃,令牌将定期刷新,当令牌即将过期时,应用程序将向身份验证服务发送一个请求,以刷新令牌,身份验证服务将验证令牌的有效性,并生成一个新的令牌。
5、令牌过期:如果令牌过期,用户将需要重新进行认证,身份验证服务将要求用户输入新的凭证,并生成一个新的令牌。
三、Java 实现单点登录
1、创建身份验证服务:我们将使用 Spring Boot 框架来创建身份验证服务,我们需要创建一个 Maven 项目,并添加以下依赖项:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
我们需要创建一个User
实体类,用于表示用户的信息:
@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; // 省略 getter 和 setter 方法 }
我们需要创建一个UserRepository
接口,用于与数据库进行交互:
@Repository public interface UserRepository extends JpaRepository<User, Long> { User findByUsername(String username); }
我们需要创建一个UserDetailsService
实现类,用于加载用户的信息:
@Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found"); } return new UserDetails() { @Override public Collection<? extends GrantedAuthority> getAuthorities() { return AuthorityUtils.NO_AUTHORITIES; } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }; } }
我们需要创建一个AuthenticationProvider
实现类,用于处理用户的认证请求:
@Service public class JwtAuthenticationProvider implements AuthenticationProvider { @Autowired private UserDetailsService userDetailsService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = authentication.getCredentials().toString(); UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (!password.equals(userDetails.getPassword())) { throw new BadCredentialsException("Invalid username or password"); } Jwt jwt = new Jwt(); jwt.setSubject(username); jwt.setExpiration(new Date(System.currentTimeMillis() + 3600000)); jwt.setIssuer("authentication-service"); String token = jwt.encodeToBase64(); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(authentication.getPrincipal())); return authenticationToken; } @Override public boolean supports(Class<?> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } }
我们需要创建一个Jwt
类,用于生成和验证 JWT 令牌:
public class Jwt { private String subject; private Date expiration; private String issuer; public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public Date getExpiration() { return expiration; } public void setExpiration(Date expiration) { this.expiration = expiration; } public String getIssuer() { return issuer; } public void setIssuer(String issuer) { this.issuer = issuer; } public String encodeToBase64() { Map<String, Object> claims = new HashMap<>(); claims.put("subject", subject); claims.put("expiration", expiration.getTime()); claims.put("issuer", issuer); return Jwts.builder() .setClaims(claims) .signWith(SignatureAlgorithm.HS512, "secret") .compact(); } public static Jwt decodeFromBase64(String token) { Claims claims = Jwts.parser() .setSigningKey("secret") .parseClaimsJws(token) .getBody(); Jwt jwt = new Jwt(); jwt.setSubject(claims.get("subject").toString()); jwt.setExpiration(new Date((Long) claims.get("expiration"))); jwt.setIssuer(claims.get("issuer").toString()); return jwt; } }
我们需要创建一个SecurityConfig
类,用于配置 Spring Security:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationProvider jwtAuthenticationProvider; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/api/**").authenticated() .and() .httpBasic(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(jwtAuthenticationProvider); } }
我们需要创建一个TokenFilter
类,用于拦截请求并验证 JWT 令牌:
@Component public class TokenFilter extends OncePerRequestFilter { @Autowired private TokenProvider tokenProvider; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = tokenProvider.resolveToken(request); if (token!= null && tokenProvider.validateToken(token)) { filterChain.doFilter(request, response); } else { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token"); } } }
2、创建应用程序:我们将使用 Spring Boot 框架来创建应用程序,我们需要创建一个 Maven 项目,并添加以下依赖项:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
我们需要创建一个User
实体类,用于表示用户的信息:
@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; // 省略 getter 和 setter 方法 }
我们需要创建一个UserRepository
接口,用于与数据库进行交互:
@Repository public interface UserRepository extends JpaRepository<User, Long> { User findByUsername(String username); }
我们需要创建一个UserDetailsService
实现类,用于加载用户的信息:
@Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found"); } return new UserDetails() { @Override public Collection<? extends GrantedAuthority> getAuthorities() { return AuthorityUtils.NO_AUTHORITIES; } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }; } }
我们需要创建一个AuthenticationProvider
实现类,用于处理用户的认证请求:
@Service public class JwtAuthenticationProvider implements AuthenticationProvider { @Autowired private UserDetailsService userDetailsService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = authentication.getCredentials().toString(); UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (!password.equals(userDetails.getPassword())) { throw new BadCredentialsException("Invalid username or password"); } Jwt jwt = new Jwt(); jwt.setSubject(username); jwt.setExpiration(new Date(System.currentTimeMillis() + 3600000)); jwt.setIssuer("authentication-service"); String token = jwt.encodeToBase64(); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(authentication.getPrincipal())); return authenticationToken; } @Override public boolean supports(Class<?> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } }
我们需要创建一个Jwt
类,用于生成和验证 JWT 令牌:
public class Jwt { private String subject; private Date expiration; private String issuer; public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public Date getExpiration() { return expiration; } public void setExpiration(Date expiration) { this.expiration = expiration; } public String getIssuer() { return issuer; } public void setIssuer(String issuer) { this.issuer = issuer; } public String encodeToBase64() { Map<String, Object> claims = new HashMap<>(); claims.put("subject", subject); claims.put("expiration", expiration.getTime()); claims.put("issuer", issuer); return Jwts.builder() .setClaims(claims) .signWith(SignatureAlgorithm.HS512, "secret") .compact(); } public static Jwt decodeFromBase64(String token) { Claims claims = Jwts.parser() .setSigningKey("secret") .parseClaimsJws(token) .getBody(); Jwt jwt = new Jwt(); jwt.setSubject(claims.get("subject").toString()); jwt.setExpiration(new Date((Long) claims.get("expiration"))); jwt.setIssuer(claims.get("issuer").toString()); return jwt; } }
我们需要创建一个SecurityConfig
类,用于配置 Spring Security:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationProvider jwtAuthenticationProvider; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/api/**").authenticated() .and() .httpBasic(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(jwtAuthenticationProvider); } }
我们需要创建一个TokenFilter
类,用于拦截请求并验证 JWT 令牌:
@Component public class TokenFilter extends OncePerRequestFilter { @Autowired private TokenProvider tokenProvider; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = tokenProvider.resolveToken(request); if (token!= null && tokenProvider.validateToken(token)) { filterChain.doFilter(request, response); } else { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token"); } } }
我们需要创建一个UserController
类,用于处理用户的请求:
@RestController @RequestMapping("/api") public class UserController { @GetMapping("/user") public User getUser() { // 获取当前用户的信息 return new User(); } }
3、创建前端页面:我们将使用 HTML 和 JavaScript 来创建前端页面,我们需要创建一个index.html
文件,用于显示登录页面:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>单点登录示例</title> </head> <body> <h2>单点登录示例</h2> <form action="/login" method="post"> <label for="username">用户名:</label><input type="text" id="username" name="username"><br> <label for="password">密码:</label><input type="password" id="password" name="password"><br> <input type="submit" value="登录"> </form> </body> </html>
我们需要创建一个app.js
文件,用于处理登录请求和获取用户信息:
// 引入 jQuery 库 import $ from 'jquery'; // 定义登录函数 function login(username, password) { $.ajax({ url: '/login', type: 'post', data: { username: username, password: password }, success: function(response) { // 登录成功,跳转到用户页面 window.location.href = '/user'; }, error: function(response) { // 登录失败,显示错误信息 alert('登录失败,请检查用户名和密码是否正确!'); } }); } // 定义获取用户信息函数 function getUserInfo() { $.ajax({ url: '/user/user', type: 'get',
评论列表