SpringSecurity详解

本文介绍如何使用 Spring Security 进行用户认证与授权,包括自定义登录页面、实现 UserDetailsService 接口进行用户验证、配置安全策略及 JWT 认证流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.springsecurity底层实现为一条过滤器链。其实就是认证和授权

2.springsecurity自带一个登录页。对登录请求入手,替换掉自带的,对输入的账号密码进行验证。

3.定义一个配置类 继承 WebSecurityConfigurerAdapter

@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/*
	 * 配置策略
	 *
	 * @param httpSecurity
	 * 
	 * @throws Exception
	 */
	@Override
	protected void configure(HttpSecurity httpSecurity) throws Exception {
		httpSecurity
				// 由于使用的是JWT,我们这里不需要csrf
				.csrf().disable()
				// 基于token,所以不需要session
		.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
				// 过滤请求
				.authorizeRequests()
				// 对于登录login 图标 静态资源允许匿名访问
				.antMatchers("/login/**").anonymous()
				.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()  //访问此地址不需要身份认证
				// 对于以上 没有添加的 都需经过过滤器认证
				// 除上面外的所有请求全部需要鉴权认证
				.anyRequest().authenticated().and().headers().frameOptions().disable();
		
	}
}

4.自定义一个对象实现UserDetails(Security提供),接下来的认证过程需要用到

import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;

@Setter
@Getter
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class SecurityUser implements UserDetails{
	

    private Integer userId;
    
    private String username;
    
    private String password;
    
    //用户权限
    private Collection<? extends GrantedAuthority> authorities;
    
	@Override
	public String getPassword() {
		return password;
	}

	@Override
	public String getUsername() {
		return username;
	}

    /**
     * 返回分配给用户的角色列表
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    /**
     * 账户是否未过期,过期无法验证
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 指定用户是否解锁,锁定的用户无法进行身份验证
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 是否可用 ,禁用的用户不能身份验证
     * @return
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
 
}

5.定义账号密码登录业务 实现UserDetailsService接口,这里的SysUserService根据自己的用户实体类

@Slf4j
@Service("userDetailsService")
public class UserDetailServiceImpl implements UserDetailsService {

	@Autowired
	private SysUserService sysUserService;

	/**
	 * 
	 * @Description: 账号密码登录业务
	 * @author: zhou
	 * @date: 2022年6月29日 下午4:39:10
	 */
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //根据登录用户名 从数据库中查询登录用户信息
		SysUser user = sysUserService.loginByUserName(username);
		if (user == null) {
			log.info("登录用户:" + username + " 不存在.");
			throw new RuntimeException("登录用户:" + username + " 不存在");
		}
		List<String> list = new ArrayList<String>(Arrays.asList("admin","test"));
		List<SimpleGrantedAuthority> authorities = list.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
	
		return new SecurityUser(user.getUserId(),username,user.getPassword(), authorities);
	}


}

5.1 注意:返回用户权限一定不能为空或者null

5.2 这里获取到用户密码  是经过加密后的密码(数据库存的密码是加密后的),下面也会讲到

6.定义自己登录业务  的实现类

	/**
	 * @Description: TODO(这里用一句话描述这个类的作用)
	 * @param:
	 * @return:
	 * @author: zhou
	 * @date: 2022年6月29日 下午8:33:54
	 */
	@Override
	public String login(String username, String password) {

		// 用户验证
		Authentication authentication = authenticationManager
				.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        //其实 就是执行 上面UserDetailServiceImpl的方法

		if (authentication == null) {
			throw new RuntimeException("登录失败");
		}
		// 存储认证信息
		SecurityContextHolder.getContext().setAuthentication(authentication);
		// 生成token
		SecurityUser userDetail = (SecurityUser) authentication.getPrincipal();

		return JWTUtils.generateToken(userDetail); //这个根据自己定义吧
	}

7.在配置类中添加上自己的登录业务。让springSecurity 根据加密规则对原生登录进行加密再与数据库中密码比对。

@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private UserDetailServiceImpl userDetailsService;

	@Override
	public void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
	}


}
### 关于Spring Security与JWT集成 在构建基于令牌的身份验证解决方案时,开发者经常会选择JSON Web Tokens (JWT)作为实现方式之一。尽管网络上存在许多自定义的令牌认证方案,在Spring Security框架内并没有提供官方的标准实现[^1]。 为了帮助理解如何将Spring Security同JWT结合起来,下面是一个简单的指南: #### 添加必要的依赖项 首先需要确保项目中包含了处理安全性和Web请求所需的库。对于Spring Boot应用来说,这通常意味着要加入`spring-boot-starter-security`以及用于解析和创建JWT的支持包,比如JJWT。另外,AOP(面向切面编程)也可能被用来简化某些操作,因此可以考虑引入`sprint-boot-starter-aop`这样的依赖[^2]。 ```xml <dependencies> <!-- Spring Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- JWT Support via JJWT library --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>${jjwt.version}</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>${jjwt.version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>${jjwt.version}</version> <scope>runtime</scope> </dependency> <!-- AOP support --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> </dependencies> ``` #### 配置Security设置 接下来就是配置Spring Security以支持JWT身份验证逻辑。可以通过扩展`WebSecurityConfigurerAdapter`并重写其方法来完成这项工作。这里的关键在于编写一个过滤器链中的处理器,它能够拦截HTTP请求并对其中携带的JWT进行解码和验证。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/auth/**").permitAll() // Allow access without authentication for login endpoint .anyRequest().authenticated(); // All other requests require auth JwtTokenFilter jwtTokenFilter = new JwtTokenFilter(authenticationManager()); http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); } } ``` #### 创建JwtTokenProvider类 这个组件负责实际生成和校验JWT字符串。它可以封装住所有跟密钥管理、签名算法有关的具体细节,并向外界暴露简单易用的方法接口。 ```java @Component public class JwtTokenProvider { private final String secretKey; private final long validityInMilliseconds; public JwtTokenProvider(@Value("${jwt.secret}") String secret, @Value("${jwt.expiration}") long validityInMilliseconds) { this.secretKey = Base64.getEncoder().encodeToString(secret.getBytes()); this.validityInMilliseconds = validityInMilliseconds; } public String createToken(String username, List<String> roles) { Claims claims = Jwts.claims().setSubject(username); claims.put("roles", roles); Date now = new Date(); Date expiryDate = new Date(now.getTime() + validityInMilliseconds); return Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, secretKey) .compact(); } public boolean validateToken(String token) { try { Jws<Claims> claims = Jwts.parser() .setSigningKey(Base64.getDecoder().decode(secretKey)) .parseClaimsJws(token); return !claims.getBody().getExpiration().before(new Date()); } catch (Exception e){ return false; } } public String getUsernameFromToken(String token) { Claims claims = Jwts.parser() .setSigningKey(Base64.getDecoder().decode(secretKey)) .parseClaimsJws(token) .getBody(); return claims.getSubject(); } } ``` #### 实现JwtTokenFilter类 该过滤器会在每次收到客户端发送过来的新请求时触发执行。它的职责是从HTTP头里提取出Bearer类型的授权凭证,再调用上面提到过的`JwtTokenProvider`来进行有效性检查。 ```java public class JwtTokenFilter extends OncePerRequestFilter { private final AuthenticationManager authenticationManager; private final JwtTokenProvider jwtTokenProvider; public JwtTokenFilter(AuthenticationManager authenticationManager, JwtTokenProvider jwtTokenProvider) { this.authenticationManager = authenticationManager; this.jwtTokenProvider = jwtTokenProvider; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String token = resolveToken(request); if (token != null && jwtTokenProvider.validateToken(token)) { Authentication auth = getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(auth); } chain.doFilter(request, response); } private String resolveToken(HttpServletRequest req) { String bearerToken = req.getHeader("Authorization"); if (bearerToken != null && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } private Authentication getAuthentication(String token) { UserDetails userDetails = myUserDetailsService.loadUserByUsername( jwtTokenProvider.getUsernameFromToken(token)); return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); } } ``` 上述代码片段展示了如何利用Spring Security框架配合第三方库(JJWT),快速搭建起一套完整的基于JWT的安全机制。当然这只是冰山一角,具体应用场景下可能还需要进一步调整和完善各个部分的功能特性。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值