springsecurity前后端分离登录认证_Spring Security 真正的前后分离实现

Spring Security网络上很多前后端分离的示例很多都不是完全的前后分离,而且大家实现的方式各不相同,有的是靠自己写拦截器去自己校验权限的,有的页面是使用themleaf来实现的不是真正的前后分离,看的越多对Spring Security越来越疑惑,此篇文章要用最简单的示例实现出真正的前后端完全分离的权限校验实现。

1. pom.xml

主要依赖是spring-boot-starter-security和jwt。

org.springframework.boot    spring-boot-starter-weborg.springframework.boot    spring-boot-starter-securityio.jsonwebtoken    jjwt-api    ${jjwt.version}io.jsonwebtoken    jjwt-impl    ${jjwt.version}io.jsonwebtoken    jjwt-jackson    ${jjwt.version}org.apache.commons    commons-lang3    3.9org.projectlombok    lombok    true

2. User

@Data@ToString@NoArgsConstructor@AllArgsConstructorpublic class User implements UserDetails {    private Long id;    private String username;    private String password;    private Boolean enabled;    private List authorities;    @Override    public Collection extends GrantedAuthority> getAuthorities() {        return this.authorities;    }    @Override    public String getPassword() {        return this.password;    }    @Override    public String getUsername() {        return this.username;    }    @Override    public boolean isAccountNonExpired() {        return true;    }    @Override    public boolean isAccountNonLocked() {        return true;    }    @Override    public boolean isCredentialsNonExpired() {        return true;    }    @Override    public boolean isEnabled() {        return this.enabled;    }}

3. UserDetailsService

@RequiredArgsConstructor@Service("userDetailsService")public class UserDetailsServiceImpl implements UserDetailsService {    @Autowired    private PasswordEncoder passwordEncoder;    @Override    public User loadUserByUsername(String username) {        List authorities = Arrays.asList(                new SimpleGrantedAuthority("user:add"),                new SimpleGrantedAuthority("user:view"),                new SimpleGrantedAuthority("user:update"));        User user = new User(1L, username, passwordEncoder.encode("123456"), true, authorities);        if (user == null) {            throw new UsernameNotFoundException("用户名或者密码错误");        }        return user;    }}

4. TokenProvider

/** * JWT Token提供器 */@Slf4j@Componentpublic class TokenProvider implements InitializingBean {    public static final String AUTHORITIES_KEY = "auth";    private JwtParser jwtParser;    private JwtBuilder jwtBuilder;    @Override    public void afterPropertiesSet() {        // 必须使用最少88位的Base64对该令牌进行编码        String secret = "必须使用最少88位的Base64对该令牌进行编码,一般是配置在application.yml中,需要预先定义好";        byte[] keyBytes = Decoders.BASE64.decode(secret);        Key key = Keys.hmacShaKeyFor(keyBytes);        jwtParser = Jwts.parserBuilder().setSigningKey(key).build();        jwtBuilder = Jwts.builder().signWith(key, SignatureAlgorithm.HS512);    }    public String createToken(Authentication authentication) {        // 获取权限列表        String authorities = authentication.getAuthorities().stream()                .map(GrantedAuthority::getAuthority)                .collect(Collectors.joining(","));        return jwtBuilder                // 加入ID确保生成的 Token 都不一致                .setId(UUID.randomUUID().toString())                // 权限列表                .claim(AUTHORITIES_KEY, authorities)                // username                .setSubject(authentication.getName())                // 过期时间                .setExpiration(DateUtils.addDays(new Date(), 1))                .compact();    }    /**     * 从token中获取认证信息     * @param token     * @return     */    public Authentication getAuthentication(String token) {        Claims claims = jwtParser.parseClaimsJws(token).getBody();        Object authoritiesStr = claims.get(AUTHORITIES_KEY);        Collection extends GrantedAuthority> authorities =                authoritiesStr != null ?                        Arrays.stream(authoritiesStr.toString().split(","))                                .map(SimpleGrantedAuthority::new)                                .collect(Collectors.toList()) : Collections.emptyList();        User principal = new User(claims.getSubject(), "******", authorities);        return new UsernamePasswordAuthenticationToken(principal, token, authorities);    }}

5. AccessDeniedHandler

@Componentpublic class JwtAccessDeniedHandler implements AccessDeniedHandler {   @Override   public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {      // 当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应      response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());   }}

6. AuthenticationEntryPoint

@Componentpublic class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {    @Override    public void commence(HttpServletRequest request,                         HttpServletResponse response,                         AuthenticationException authException) throws IOException {        // 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401响应        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException == null ? "Unauthorized" : authException.getMessage());    }}

7. TokenFilter

@Slf4j@Componentpublic class TokenFilter extends GenericFilterBean {    private TokenProvider tokenProvider;    public TokenFilter(TokenProvider tokenProvider) {        this.tokenProvider = tokenProvider;    }    @Override    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)            throws IOException, ServletException {        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;        String bearerToken = httpServletRequest.getHeader("Authorization");        String token = null;        if (!StringUtils.isEmpty(bearerToken) && bearerToken.startsWith("Bearer")) {            token = bearerToken.replace("Bearer", "");        }        if (!StringUtils.isEmpty(token)) {            Authentication authentication = tokenProvider.getAuthentication(token);            SecurityContextHolder.getContext().setAuthentication(authentication);        }        filterChain.doFilter(servletRequest, servletResponse);    }}

8. WebMvcConfigurer

@Configuration@EnableWebMvcpublic class WebMvcConfigurerAdapter implements WebMvcConfigurer {    @Bean    public CorsFilter corsFilter() {        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();        CorsConfiguration config = new CorsConfiguration();        config.setAllowCredentials(true);        config.addAllowedOrigin("*");        config.addAllowedHeader("*");        config.addAllowedMethod("*");        source.registerCorsConfiguration("/**", config);        return new CorsFilter(source);    }}

9. TokenConfigurer

@RequiredArgsConstructorpublic class TokenConfigurer extends SecurityConfigurerAdapter {    private TokenProvider tokenProvider;    public TokenConfigurer(TokenProvider tokenProvider) {        this.tokenProvider = tokenProvider;    }    @Override    public void configure(HttpSecurity http) {        TokenFilter customFilter = new TokenFilter(tokenProvider);        http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);    }}

10. SecurityConfig

@Configuration@EnableWebSecurity@RequiredArgsConstructor@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter {    @Autowired    private CorsFilter corsFilter;    @Autowired    private TokenProvider tokenProvider;    @Autowired    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;    @Autowired    private JwtAccessDeniedHandler jwtAccessDeniedHandler;    @Bean    public GrantedAuthorityDefaults grantedAuthorityDefaults() {        // 去除 ROLE_ 前缀        return new GrantedAuthorityDefaults("");    }    @Bean    public PasswordEncoder passwordEncoder() {        // 密码加密方式        return new BCryptPasswordEncoder();    }    @Override    protected void configure(HttpSecurity httpSecurity) throws Exception {        httpSecurity                // 禁用 CSRF                .csrf().disable()                .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)                // 授权异常                .exceptionHandling()                .authenticationEntryPoint(jwtAuthenticationEntryPoint)                .accessDeniedHandler(jwtAccessDeniedHandler)                // 防止iframe 造成跨域                .and()                .headers()                .frameOptions()                .disable()                // 不创建会话                .and()                .sessionManagement()                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)                .and()                .authorizeRequests()                // 静态资源等等                .antMatchers(                        HttpMethod.GET,                        "/*.html",                        "/**/*.html",                        "/**/*.css",                        "/**/*.js",                        "/webSocket/**"                ).permitAll()                // swagger 文档                .antMatchers("/swagger-ui.html").permitAll()                .antMatchers("/swagger-resources/**").permitAll()                .antMatchers("/webjars/**").permitAll()                .antMatchers("/*/api-docs").permitAll()                // 文件                .antMatchers("/avatar/**").permitAll()                .antMatchers("/file/**").permitAll()                // 阿里巴巴 druid                .antMatchers("/druid/**").permitAll()                // 放行OPTIONS请求                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()                // 不需要认证的接口                .antMatchers("/auth/login").permitAll()                // 所有请求都需要认证                .anyRequest().authenticated()                .and().apply(securityConfigurerAdapter());    }    private TokenConfigurer securityConfigurerAdapter() {        return new TokenConfigurer(tokenProvider);    }}

11. AuthController

@RestController@RequestMapping("/auth")public class AuthController {    @Autowired    private TokenProvider tokenProvider;    @Autowired    private AuthenticationManagerBuilder authenticationManagerBuilder;    @RequestMapping("/login")    public String login() {        UsernamePasswordAuthenticationToken authenticationToken =                new UsernamePasswordAuthenticationToken("monday", "123456");        // 会调用 UserDetailsService.loadUserByUsername        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);        SecurityContextHolder.getContext().setAuthentication(authentication);        String token = tokenProvider.createToken(authentication);        return token;    }}

12. UserController

@RestController@RequestMapping("/user")public class UserController {    @RequestMapping("/add")    @PreAuthorize("hasAnyRole('user:add')")    public String add() {        return "user:add";    }    @RequestMapping("/update")    @PreAuthorize("hasAnyRole('user:update')")    public String update() {        return "user:update";    }    @RequestMapping("/view")    @PreAuthorize("hasAnyRole('user:view')")    public String view() {        return "user:view";    }    @RequestMapping("/delete")    @PreAuthorize("hasAnyRole('user:delete')")    public String delete() {        return "user:delete";    }}
8659f4c1c93b6f207d95a615dd5f1108.png

访问有权限的接口。

e928fc37edfd7bafa6788ddc62e5f5df.png

访问没有权限的接口被拒绝。

50eebc0a662c57ae29eb15074618c6fe.png

13. Spring Security 认证和授权原理

  1. 用户登录会调用UserDetailsService对用户名和密码进行检查,返回用户名、密码、权限字符串列表,认证成功后就会将用户信息放在安全上下文中SecurityContext。
  2. 当用户访问带有权限的接口,Spring Security会调用TokenFilter获取到token,解析token并存入到安全上下文SecurityContext中,然后检查@PreAuthorize("hasAnyRole('user:add')")配置的权限字符串是否在SecurityContext中用户的authorities列表中,如果在表示有权限放行,如果不在表示没有权限,则执行AccessDeniedHandler返回。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值