springboot整合springSecurity

本文详细描述了如何在SpringBoot项目中,移除默认security登录页面,自定义配置用户认证,使用Database中的账号密码,以及实现JWTtoken验证的过程,包括`UserDetailsService`、`SecurityConfig`和自定义`JwtAuthenticationTokenFilter`的配置和使用。
摘要由CSDN通过智能技术生成

springboot项目引入security后,security就会创建默认登陆页面,对用户身份权限认证,还会把生成的用户user的密码输出在控制台,只要访问了项目资源就会自动跳转到他的默认登录页面,只有使用他的密码登录后才会去访问我们接口

在前后端分离的项目中,不需要security提供的登陆页面,他的这些配置不符合我们的需求,此时就需要自定义配置,实现我们的需求,比如通过数据库中的账号密码实现登录,而不是通过它提供的账号密码登录。

一. security也提供了这种用户认证的方式。

首先security有一个SecurityContextHolder对象,用于管理security的上下文SeucirtyContext,

SeucirtyContext中存储着Authentication,也就是登录用户的信息

获取SecurityContextHolder对象:

UsernamePasswordAuthenticationToken authentication =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();

表示通过SecurityContextHolder(上下文管理对象)获取SeucirtyContext(上下文对象)获取Authentication,他里面存储了三个信息,

 1、Principal:用户信息,没有认证时一般是用户名,认证后一般是用户对象

​ 2、Credentials:用户凭证,一般是密码

​ 3、Authorities:用户权限。

二 .security是通过AuthenticationManager的authenticate完成用户认证。

三.那么AuthenticationManager是如何校验用户信息的呢,

首先我们需要实现一个类实现security中的UserDetialsService接口,只需要实现一个方法

loadUserByUsername,让此方法能根据用户名查询出用户对象,如下:

此方法返回一个Security中的用户数据接口 UserDetails 的实现类对象,所以我么也需要自定义一个实现类
UserDetailsImpl来实现UserDetails接口,用于提供用户基本属性,如账号密码,其他方法一般情况下返回true,如下:

当然此方法也可以重写此接口方法实现其他自定义属性逻辑。这里就不介绍。

此时就完成了在security获取用户信息的基础条件

四. 然后就该实现SecurityConfig的配置类,如下:



@Configuration
@EnableWebSecurity     //Security配置类
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired   //jwt自定义过滤器
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Bean   //密码编译器,加密密码
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                //表示以下路径不需要通过Security验证,可以直接访问
                .antMatchers("/api/user/account/login", "/api/user/account/register/","enclosures/updateEnclosures/").permitAll()
                //表示以下路径代理到127.0.0.1进行处理
//                .antMatchers("/pk/start/game/","/pk/receive/bot/move/").hasIpAddress("127.0.0.1")
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .antMatchers("/v2/api-docs", "/swagger-resources/configuration/ui",
                        "/swagger-resources", "/swagger-resources/configuration/security",
                        "/swagger-ui.html", "/webjars/**","/error").permitAll()
                .anyRequest().authenticated();
        //把jwt自定义的过滤器添加到Security的链中
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

}

五. 自定义认证过滤器

我们需要自定义一个过滤器,实现在访问securityu允许的公共接口外,去验证是否符合权限,也就是去获取请求头中的token是否合法,对token进行解析取出其中的信息,获取对应的登录对象。然后封装Authentication对象存入SecurityContextHolder。如下:



@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private UserMapper userMapper;

    @Override
    protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
        //获取前端请求头中的的Authorization的值
        String token = request.getHeader("Authorization");
        System.out.println(token);
        System.out.println(request.getRequestURI());

        //判断token是否是"Bearer "开头和token是否为空
        if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
            //如果为token不合法则进行下一个拦截请求,这个不放行
            filterChain.doFilter(request, response);
            LocalDateTime.now();
            return;
        }
        //token如果合法则除去开头的"Bearer ",留下真正的token
        token = token.substring(7);

        String userid;
        try {
            //通过jwt工具类解析token获取token中的userid
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        //通过token中解析出的id从数据库查询user是否存在
        User user = userMapper.selectById(Integer.parseInt(userid));

        if (user == null) {
            throw new RuntimeException("用户名未登录");
        }

        //UserDetailsImpl是Security自带的封装用户信息等类,
        UserDetailsImpl loginUser = new UserDetailsImpl(user);

        //通过UsernamePasswordAuthenticationToken类表示从封装的用户类信息的实例中获取用户信息,
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, null);

          //把用户信息存储到Security上下文
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        filterChain.doFilter(request, response);
    }
}

因为过滤器需要对token进行解析获取用户信息,所以也需要一个jwt工具类,用于生成解析token信息等:如下


@Component
public class JwtUtil {
    public static final long JWT_TTL = 60 * 60 * 1000L * 24 ; //token过期时间
    public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";  //生成token密钥

    public static String getUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    //创建返回jwt
    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
        return builder.compact();
    }
    //构建jwt
    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if (ttlMillis == null) {
            ttlMillis = JwtUtil.JWT_TTL;
        }

        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)
                .setSubject(subject)
                .setIssuer("sg")
                .setIssuedAt(now)
                .signWith(signatureAlgorithm, secretKey)
                .setExpiration(expDate);
    }
    //生成和验证jwt的密钥
    public static SecretKey generalKey() {
        byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
    }
    //解析jwt
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parserBuilder()
                .setSigningKey(secretKey)
                .build()
                .parseClaimsJws(jwt)
                .getBody();
    }
}

六. 登录测试了。

首先是登陆接口。

因为配置类中对登录接口的路径进行了放行,所以不会进入过滤器进行token验证,而是直接执行,

然后service逻辑如下,如果是合法的登录用户,则通过jwt工具类生成含有用户唯一标示信息的token返回给用户,用户需要在访问除了公共接口外的所有接口中带上此token

 

七:获取信息测试

controller接口为

此接口没有在配置类中放行,所以需要进入过滤器,验证token信息,如果正确就进入service执行相应逻辑,

否则抛出异常,

service逻辑如下,

此时就完成闭环。个人学习总结,如有错误不足,感谢指正

  • 27
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值