Spring Boot 集成 Spring Security 教程

1 引包

    implementation("org.springframework.boot:spring-boot-starter-security")//security
    implementation('io.jsonwebtoken:jjwt:0.9.1')//jwt

2 配置 SecurityConfig

package com.microcardio.mp.common.config.auth;

import com.microcardio.mp.common.config.auth.exception.AdminAuthenticationEntryPoint;
import com.microcardio.mp.common.config.auth.exception.UrlAccessDeniedHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * <p> Security 核心配置类 </p>
 *
 * @author aaaak
 * @since 2021-02-26
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 访问权限认证异常处理
     */
    private final AdminAuthenticationEntryPoint adminAuthenticationEntryPoint;


    /**
     * 角色权限认证异常处理
     */
    private final UrlAccessDeniedHandler urlAccessDeniedHandler;

    /**
     * 权限配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //允许该路径 类型请求通过
        http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll();
        //正则匹配 public 开头的接口都要走 认证
        http.authorizeRequests()
                .regexMatchers("^/1admin").access("hasRole('ADMIN') or hasRole('USER')")
                .antMatchers("/auth/user/**").permitAll()//后台登录 跳过 auth
                .antMatchers("/wx/msg/**").permitAll()//微信消息服务 跳过  auth
                .antMatchers("/api/**").permitAll() // 微信端 接口 跳过 auth
                .antMatchers("/public/**").permitAll() // 公共接口  跳过 auth
                .anyRequest().authenticated();

        // 未登录的请求,请求失败处理
        http.exceptionHandling().authenticationEntryPoint(adminAuthenticationEntryPoint);
        // 未授权的请求,请求失败处理
        http.exceptionHandling().accessDeniedHandler(urlAccessDeniedHandler);
        // 认证策略, jwt, basic auth, digest aut 中选取 jwt 认证
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);


        //禁用CSRF 允许跨域
        http.csrf().disable().cors();
        //除非明确列出,否则不要使用任何默认标题。
        http.headers().defaultsDisabled().cacheControl();
        //自定义权限拦截 jwt
        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        // TODO: 2021/3/2  后期有需求再添加动态角色权限配置
    }

    /**
     * 暂时性 将 登录用户信息存在内存中 测试
     *
     */
//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        // 在内存中配置用户,配置多个用户调用`and()`方法
//        auth.inMemoryAuthentication()
//                .passwordEncoder(passwordEncoder()) // 指定加密方式
//                .withUser("admin").password(passwordEncoder().encode("wxby123456")).roles("ADMIN")
//                .and()
//                .withUser("test").password(passwordEncoder().encode("wxby123456")).roles("USER");
//
//    }

    @Bean 
    public PasswordEncoder passwordEncoder() {
        // BCryptPasswordEncoder:Spring Security 提供的加密工具,可快速实现加密加盐
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    @Bean
    public AuthTokenFilter authenticationJwtTokenFilter() {
        return new AuthTokenFilter();
    }


    /**
     * 忽略拦截url或静态资源文件夹 - web.ignoring(): 会直接过滤该url - 将不会经过Spring Security过滤器链  而且 只能过滤GET 请求
     * http.permitAll(): 不会绕开springsecurity验证,相当于是允许该路径通过
     *
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(HttpMethod.GET,
                "/favicon.ico",
                "/actuator/**",
                "/v2/api-docs",
                "/configuration/ui",
                "/swagger-resources/**",
                "/configuration/security",
                "/swagger-ui.html",
                "/webjars/**",
                "/wxAuth/**",
                "/api/**");
    }

}

3配置 认证授权 捕获 返回信息类

package com.microcardio.mp.common.config.auth.exception;

import com.microcardio.mp.common.entity.JsonResult;
import com.microcardio.mp.common.util.ResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 认证失败处理类 返回未授权
 * 用来解决匿名用户访问无权限资源时的异常
 */
@Configuration
@Slf4j
public class AdminAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
        // 未登录 或 token过期
        if (e instanceof InsufficientAuthenticationException) {
            ResponseUtils.out(response, JsonResult.failed(-100, "令牌失效 请登陆 获取 新的访问令牌"));
        } else if (e instanceof BadCredentialsException) {
            ResponseUtils.out(response, JsonResult.failed(-100, "认证失败,密码错误"));
        }
        log.warn("adapter Exception: {}", e.getClass().getName());

    }
}

package com.microcardio.mp.common.config.auth.exception;

import com.microcardio.mp.common.entity.JsonResult;
import com.microcardio.mp.common.util.ResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.file.AccessDeniedException;

/**
 * <p> 认证url权限 - 登录后访问接口无权限 - 自定义403无权限响应内容 </p>
 */
@Configuration
@Slf4j
public class UrlAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, org.springframework.security.access.AccessDeniedException e) throws IOException, ServletException {
        log.warn("UrlAccess Exception: {}", e.getClass().getName());
        ResponseUtils.out(response, JsonResult.failed(-101, e.getMessage()));
    }
}

4编写权限验证 Filter

package com.microcardio.mp.common.config.auth;

import com.microcardio.mp.admin.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class AuthTokenFilter 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);
        }

        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;
    }
}

5 编写用户信息 实体类 实现UserDetails

package com.microcardio.mp.admin.service;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.microcardio.mp.admin.entity.User;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Getter
@Setter
public class UserDetailsImpl implements UserDetails {
    private static final long serialVersionUID = 1L;

    private Integer id;

    private String username;

    private String email;

    @JsonIgnore
    private String password;

    private Collection<? extends GrantedAuthority> authorities;

    public UserDetailsImpl(Integer id, String username, String email, String password,
                           Collection<? extends GrantedAuthority> authorities) {
        this.id = id;
        this.username = username;
        this.email = email;
        this.password = password;
        this.authorities = authorities;
    }

    public static UserDetailsImpl build(User user) {
        List<GrantedAuthority> authorities = user.getRoles().stream()
                .map(role -> new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());

        return new UserDetailsImpl(
                user.getId(),
                user.getUsername(),
                user.getEmail(),
                user.getPassword(),
                authorities);
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    public Integer getId() {
        return id;
    }

    public String getEmail() {
        return email;
    }

    @Override
    public String getPassword() {
        return password;
    }

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

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        UserDetailsImpl user = (UserDetailsImpl) o;
        return Objects.equals(id, user.id);
    }
}

6 编写用户信息 Service类 实现UserDetailsService

此处是写死的没有查数据库

package com.microcardio.mp.admin.service.impl;

import com.microcardio.mp.admin.entity.Role;
import com.microcardio.mp.admin.entity.User;
import com.microcardio.mp.admin.service.UserDetailsImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashSet;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    PasswordEncoder passwordEncoder;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = new User();

        if ("admin".equals(username)) {
            HashSet<Role> roles = new HashSet<>();
            roles.add(new Role().setName("ADMIN").setId(1));

            user.setId(1).setUsername("admin").setPassword(passwordEncoder.encode("123456")).setEmail("admin@microcardio.com").setRoles(roles);
        } else {
            HashSet<Role> roles = new HashSet<>();
            roles.add(new Role().setName("USER").setId(2));

            user.setId(1).setUsername("user").setPassword(passwordEncoder.encode("123456")).setEmail("admin@microcardio.com").setRoles(roles);
        }
        return UserDetailsImpl.build(user);
    }

}

7 JwtUtils工具类 ,用于Signature的生成

package com.microcardio.mp.common.config.auth;

import com.microcardio.mp.admin.service.UserDetailsImpl;
import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.security.core.userdetails.User;

import java.util.Date;


@Component
public class JwtUtils {
    private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);

    @Value("test-aaaak-SecretKey")
    private String jwtSecret;

    @Value("86400000")
    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 String getUserNameFromJwtToken(String token) {
        return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
    }

    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;
    }
}

8 测试认证登录

package com.microcardio.mp.admin.controller;

import com.microcardio.mp.admin.entity.JwtResponse;
import com.microcardio.mp.admin.entity.User;
import com.microcardio.mp.admin.service.UserDetailsImpl;
import com.microcardio.mp.admin.service.impl.UserDetailsServiceImpl;
import com.microcardio.mp.common.config.auth.JwtUtils;
import com.microcardio.mp.common.entity.JsonResult;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/auth/user")
@Api(tags = "admin 后台登录模块")
public class AuthController {
    @Autowired
    AuthenticationManager authenticationManager;


    @Autowired
    PasswordEncoder encoder;

    @Autowired
    JwtUtils jwtUtils;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    PasswordEncoder passwordEncoder;


    /**
     * 登录 认证
     *
     * @param loginRequest
     * @return
     */
    @PostMapping("/signin")
    public JsonResult<JwtResponse> authenticateUser(@Valid @RequestBody User loginRequest) {

        Authentication authentication = authenticationManager.authenticate(

                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));

        SecurityContextHolder.getContext().setAuthentication(authentication);
        String jwt = jwtUtils.generateJwtToken(authentication);

        UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
        List<String> roles = user.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());

        return JsonResult.success(new JwtResponse(jwt, user.getId(), user.getUsername(), user.getEmail(), roles));
    }



}

9 验证 是否成功

Spring Security 登录

在这里插入图片描述

Spring Security 访问需要 验证身份接口

失败例子 未 头部带token
在这里插入图片描述
成功例子 头部带token

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10 Swagger 集成 Spring Security

 @Bean()
    public Docket authDocket() {
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("xxxx-xx端接口文档")
                .description("Restful API")
                .version("1.0")
                .build();
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo)
                .consumes(DEFAULT_PRODUCES_AND_CONSUMES)
                .produces(DEFAULT_PRODUCES_AND_CONSUMES)
                .groupName("管理后台接口")
                .securityContexts(Lists.newArrayList(securityContext()))
                .securitySchemes(Lists.newArrayList(securityScheme()))
                .useDefaultResponseMessages(false)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(Predicates.or(PathSelectors.regex("/admin/*.*"),PathSelectors.regex("/auth/*.*")))
                .build();
    }

    private SecurityContext securityContext() {
        return SecurityContext.builder().securityReferences(defaultAuth()).build();
    }

    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        return Arrays.asList(new SecurityReference("JWT", authorizationScopes));
    }

    private ApiKey securityScheme() {
        return new ApiKey("JWT", "Authorization", "header");
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值