SpringSecurity如何高效应用于前后端分离项目的精品实践?

2026-05-28 00:421阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1452个文字,预计阅读时间需要6分钟。

SpringSecurity如何高效应用于前后端分离项目的精品实践?

原理+用户登录请求流程:通过默认用户登录,进入Spring Security管理的用户登录请求流程:入门示例:使用Spring Security提供的登录页面,实现真正的登录功能。第一步:自定义UserDetailsService+@Servicepublic class WegoUserDetailsService{

原理

由默认用户登录得到SpringSecurity下用户登录的请求流程:

入门示例:使用SpringSecurity提供的登录页面,实现真正的登录功能

第一步:自定义UserDetailsService

@Service public class WegoUserDetailsService implements UserDetailsService { @Resource private MemberMapper memberMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //认证:根据用户名去查询对应的用户信息 LambdaQueryWrapper<Member> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Member::getUsername,username); Member member = memberMapper.selectOne(queryWrapper); //如果没有查询到用户抛出异常 if(member == null){ throw GlobalException.builder().code(401).msg("用户名或密码不正确").build(); } // TODO: 授权:查询用户的权限信息 // 将用户信息+权限信息封装成UserDetails对象 LoginMemberDetails loginMemberDetails = new LoginMemberDetails(); loginMemberDetails.setMember(member); return loginMemberDetails; } }

第二步:自定义UserDetails:

public class LoginMemberDetails implements UserDetails { @Setter private Member member; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } /** * 登录密码 * @return */ @Override public String getPassword() { return member.getPassword(); } /** * 登录账户 * @return */ @Override public String getUsername() { return member.getUsername(); } /** * 返回对象是否未过期 * @return */ @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }

第三步:测试

为了测试,修改待登录用户的密码为:{noop}明文密码,比如:

密码加密存储

默认使用的PasswordEncoder要求数据库中的密码格式为:{id}password。它会根据id去判断密码的加密方式,但是我们一般不会采用这种方式,所以就需要替换PasswordEncoder。 实际项目中推荐使用BCryptPasswordEncoder。只需要把BCryptPasswordEncoder对象注入到Spring容器中,SpringSecurity就会使用该PasswordEncoder进行密码加密。 创建SpringSecurity配置文件,在其中指定加密方式:

@Configuration public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { /** * 密码加密器 * @return */ @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }

将数据库中的密码改成加密后的内容,然后测试就可以了。

用户登录:不再使用默认的用户user+随机字符串登录,使用我们自己数据库中的用户名+密码登录。

SpringSecurity需要对自定义的登录接口放行,让用户可以直接访问到这个接口。 在登录接口中需要通过AuthenticationManager的authenticate()方法进行用户认证,故需要先在SpringSecurity配置文件中把AuthenticationManager注入容器。 认证成功后生成一个jwt,在登录成功后的响应中返回。 为了让用户下回请求时能够通过jwt快速识别出用户,我们将用户信息缓存到Redis中,将用户的id作为key。

第一步:自定义UserDetails类:

@Getter @Setter @ToString public class WegoUserDetails implements UserDetails { private User user; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } @Override public String getPassword() { if(user == null){ return null; } return user.getPassword(); } @Override public String getUsername() { if(user == null){ return null; } 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; } }

第二步:自定义UserDatailsService

在其中实现根据用户名查找用户,并将用户的信息封装成我们自己的UserDatails对象并返回

SpringSecurity如何高效应用于前后端分离项目的精品实践?

@Service public class WegoUserDetailsService implements UserDetailsService { @Resource private UserService userService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userService.getOne(Wrappers.<User>query().eq("username", username)); //TODO:查询权限信息 WegoUserDetails userDetails = new WegoUserDetails(); userDetails.setUser(user); return userDetails; } }

第三步:修改SpringSecurity配置类

在系统注入AuthenticationManager对象并放行登录接口user/login:

@Configuration public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { /** * 认证用:最终调用DetailsService的具体的登录逻辑 * @return * @throws Exception */ @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity github.com/jwtk/jjwt#features // SignatureAlgorithm.HS256:指定签名的时候使用的签名算法,也就是header那部分 .signWith(key, SignatureAlgorithm.HS256) // iat: jwt的签发时间 .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + this.expiration * 1000)); return jwtBuilder; } /** * 生成Token令牌 * * @param username 用户名 * @return 令牌Token */ public String generateToken(String username) { Map<String, Object> claims = new HashMap<>(); claims.put("sub", username); claims.put("created", new Date()); return getJwtBuilder(claims).compact(); } public String generateToken(Serializable id) { Map<String, Object> claims = new HashMap<>(); claims.put("sub", id); claims.put("created", new Date()); return getJwtBuilder(claims).compact(); } /** * 从token中获取数据声明claim * * @param token 令牌token * @return 数据声明claim */ public Claims getClaimsFromToken(String token) { try { SecretKey key = getSecretKey(secret); Claims claims = Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token).getBody(); return claims; } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) { log.error("token解析错误", e); throw new IllegalArgumentException("Token invalided."); } } /** * 从token中获取登录用户名 * * @param token 令牌 * @return 用户名 */ public String getSubjectFromToken(String token) { String subject; try { Claims claims = getClaimsFromToken(token); subject = claims.getSubject(); } catch (Exception e) { subject = null; } return subject; } /** * 获取token的过期时间 * * @param token token * @return 过期时间 */ public Date getExpirationFromToken(String token) { return getClaimsFromToken(token).getExpiration(); } public String getUserRole(String token) { return (String) getClaimsFromToken(token).get("role"); } /** * 判断token是否过期 * * @param token 令牌 * @return 是否过期:已过期返回true,未过期返回false */ public Boolean isTokenExpired(String token) { Date expiration = getExpirationFromToken(token); return expiration.before(new Date()); } /** * 验证令牌:判断token是否非法 * * @param token 令牌 * @param username 用户名 * @return 如果token未过期且合法,返回true,否则返回false */ public Boolean validateToken(String token, String username) { //如果已经过期返回false if (isTokenExpired(token)) { return false; } String usernameFromToken = getSubjectFromToken(token); return username.equals(usernameFromToken); } }

第二步:修改LoginController

在login()方法中,当用户登录成功时,生成token,并放到返回结果中

@RestController @RequestMapping("/user") public class LoginController { @Resource private JwtUtil jwtUtil; @Resource private AuthenticationManager authenticationManager; @PostMapping("/login") public Result login(@RequestBody User user) { UsernamePasswordAuthenticationToken passwordAuthenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()); Authentication authenticate = authenticationManager.authenticate(passwordAuthenticationToken); if(authenticate == null){ return ResultUtil.error(401,"error"); } user = ((WegoUserDetails)authenticate.getPrincipal()).getUser(); String token = jwtUtil.generateToken("user:" + user.getId()); return ResultUtil.success().addData("token",token); } }

第三步:修改application.yml,其中添加jwt的配置信息:

jwt: # 为JWT基础信息加密和解密的密钥,长度需要大于等于43 # 在实际生产中通常不直接写在配置文件里面。而是通过应用的启动参数传递,并且需要定期修改 secret: oQZSeguYloAPAmKwvKqqnifiQatxMEPNOvtwPsCLasd # JWT令牌的有效时间,单位秒,默认2周 expiration: 1209600 token: Authorization

第四步:测试:

token认证过滤器

第一步:自定义过滤器

@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Resource private ByteRedisUtil<WegoUserDetails> wegoUserDetailsByteRedisUtil; @Resource private JwtUtil jwtUtil; @Override protected void doFilterInternal(HttpServletRequest www.558idc.com/hw.html处的文章,转载请说明出处】

本文共计1452个文字,预计阅读时间需要6分钟。

SpringSecurity如何高效应用于前后端分离项目的精品实践?

原理+用户登录请求流程:通过默认用户登录,进入Spring Security管理的用户登录请求流程:入门示例:使用Spring Security提供的登录页面,实现真正的登录功能。第一步:自定义UserDetailsService+@Servicepublic class WegoUserDetailsService{

原理

由默认用户登录得到SpringSecurity下用户登录的请求流程:

入门示例:使用SpringSecurity提供的登录页面,实现真正的登录功能

第一步:自定义UserDetailsService

@Service public class WegoUserDetailsService implements UserDetailsService { @Resource private MemberMapper memberMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //认证:根据用户名去查询对应的用户信息 LambdaQueryWrapper<Member> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Member::getUsername,username); Member member = memberMapper.selectOne(queryWrapper); //如果没有查询到用户抛出异常 if(member == null){ throw GlobalException.builder().code(401).msg("用户名或密码不正确").build(); } // TODO: 授权:查询用户的权限信息 // 将用户信息+权限信息封装成UserDetails对象 LoginMemberDetails loginMemberDetails = new LoginMemberDetails(); loginMemberDetails.setMember(member); return loginMemberDetails; } }

第二步:自定义UserDetails:

public class LoginMemberDetails implements UserDetails { @Setter private Member member; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } /** * 登录密码 * @return */ @Override public String getPassword() { return member.getPassword(); } /** * 登录账户 * @return */ @Override public String getUsername() { return member.getUsername(); } /** * 返回对象是否未过期 * @return */ @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }

第三步:测试

为了测试,修改待登录用户的密码为:{noop}明文密码,比如:

密码加密存储

默认使用的PasswordEncoder要求数据库中的密码格式为:{id}password。它会根据id去判断密码的加密方式,但是我们一般不会采用这种方式,所以就需要替换PasswordEncoder。 实际项目中推荐使用BCryptPasswordEncoder。只需要把BCryptPasswordEncoder对象注入到Spring容器中,SpringSecurity就会使用该PasswordEncoder进行密码加密。 创建SpringSecurity配置文件,在其中指定加密方式:

@Configuration public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { /** * 密码加密器 * @return */ @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }

将数据库中的密码改成加密后的内容,然后测试就可以了。

用户登录:不再使用默认的用户user+随机字符串登录,使用我们自己数据库中的用户名+密码登录。

SpringSecurity需要对自定义的登录接口放行,让用户可以直接访问到这个接口。 在登录接口中需要通过AuthenticationManager的authenticate()方法进行用户认证,故需要先在SpringSecurity配置文件中把AuthenticationManager注入容器。 认证成功后生成一个jwt,在登录成功后的响应中返回。 为了让用户下回请求时能够通过jwt快速识别出用户,我们将用户信息缓存到Redis中,将用户的id作为key。

第一步:自定义UserDetails类:

@Getter @Setter @ToString public class WegoUserDetails implements UserDetails { private User user; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } @Override public String getPassword() { if(user == null){ return null; } return user.getPassword(); } @Override public String getUsername() { if(user == null){ return null; } 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; } }

第二步:自定义UserDatailsService

在其中实现根据用户名查找用户,并将用户的信息封装成我们自己的UserDatails对象并返回

SpringSecurity如何高效应用于前后端分离项目的精品实践?

@Service public class WegoUserDetailsService implements UserDetailsService { @Resource private UserService userService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userService.getOne(Wrappers.<User>query().eq("username", username)); //TODO:查询权限信息 WegoUserDetails userDetails = new WegoUserDetails(); userDetails.setUser(user); return userDetails; } }

第三步:修改SpringSecurity配置类

在系统注入AuthenticationManager对象并放行登录接口user/login:

@Configuration public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { /** * 认证用:最终调用DetailsService的具体的登录逻辑 * @return * @throws Exception */ @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity github.com/jwtk/jjwt#features // SignatureAlgorithm.HS256:指定签名的时候使用的签名算法,也就是header那部分 .signWith(key, SignatureAlgorithm.HS256) // iat: jwt的签发时间 .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + this.expiration * 1000)); return jwtBuilder; } /** * 生成Token令牌 * * @param username 用户名 * @return 令牌Token */ public String generateToken(String username) { Map<String, Object> claims = new HashMap<>(); claims.put("sub", username); claims.put("created", new Date()); return getJwtBuilder(claims).compact(); } public String generateToken(Serializable id) { Map<String, Object> claims = new HashMap<>(); claims.put("sub", id); claims.put("created", new Date()); return getJwtBuilder(claims).compact(); } /** * 从token中获取数据声明claim * * @param token 令牌token * @return 数据声明claim */ public Claims getClaimsFromToken(String token) { try { SecretKey key = getSecretKey(secret); Claims claims = Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token).getBody(); return claims; } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) { log.error("token解析错误", e); throw new IllegalArgumentException("Token invalided."); } } /** * 从token中获取登录用户名 * * @param token 令牌 * @return 用户名 */ public String getSubjectFromToken(String token) { String subject; try { Claims claims = getClaimsFromToken(token); subject = claims.getSubject(); } catch (Exception e) { subject = null; } return subject; } /** * 获取token的过期时间 * * @param token token * @return 过期时间 */ public Date getExpirationFromToken(String token) { return getClaimsFromToken(token).getExpiration(); } public String getUserRole(String token) { return (String) getClaimsFromToken(token).get("role"); } /** * 判断token是否过期 * * @param token 令牌 * @return 是否过期:已过期返回true,未过期返回false */ public Boolean isTokenExpired(String token) { Date expiration = getExpirationFromToken(token); return expiration.before(new Date()); } /** * 验证令牌:判断token是否非法 * * @param token 令牌 * @param username 用户名 * @return 如果token未过期且合法,返回true,否则返回false */ public Boolean validateToken(String token, String username) { //如果已经过期返回false if (isTokenExpired(token)) { return false; } String usernameFromToken = getSubjectFromToken(token); return username.equals(usernameFromToken); } }

第二步:修改LoginController

在login()方法中,当用户登录成功时,生成token,并放到返回结果中

@RestController @RequestMapping("/user") public class LoginController { @Resource private JwtUtil jwtUtil; @Resource private AuthenticationManager authenticationManager; @PostMapping("/login") public Result login(@RequestBody User user) { UsernamePasswordAuthenticationToken passwordAuthenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()); Authentication authenticate = authenticationManager.authenticate(passwordAuthenticationToken); if(authenticate == null){ return ResultUtil.error(401,"error"); } user = ((WegoUserDetails)authenticate.getPrincipal()).getUser(); String token = jwtUtil.generateToken("user:" + user.getId()); return ResultUtil.success().addData("token",token); } }

第三步:修改application.yml,其中添加jwt的配置信息:

jwt: # 为JWT基础信息加密和解密的密钥,长度需要大于等于43 # 在实际生产中通常不直接写在配置文件里面。而是通过应用的启动参数传递,并且需要定期修改 secret: oQZSeguYloAPAmKwvKqqnifiQatxMEPNOvtwPsCLasd # JWT令牌的有效时间,单位秒,默认2周 expiration: 1209600 token: Authorization

第四步:测试:

token认证过滤器

第一步:自定义过滤器

@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Resource private ByteRedisUtil<WegoUserDetails> wegoUserDetailsByteRedisUtil; @Resource private JwtUtil jwtUtil; @Override protected void doFilterInternal(HttpServletRequest www.558idc.com/hw.html处的文章,转载请说明出处】