自己开发完整项目一、登录功能-04(集成jwt)

一、说明

前面文章我们写到了通过数据库查询出用户信息并返回,那么在真实的项目中呢,后端是需要给前端返回一个tocken,当前端通过登录功能认证成功之后,我们后端需要将用户信息和权限整合成一个tocken返回给前端,当前端再次访问别的接口的时候,需要携带着tocken,请求到达后端的时候,后端需要从前端传递过来的tocken中解析出用户信息和权限,判断是否可以访问。


实现思路:在登录中的查询用户中,将权限也查询出来,随后通过jwt来生成tocken和解析tocken

二、 编写jwt工具类(生成tocken和解析tocken)

package com.ljy.myspringbootlogin.utils;

import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.Map;

@Component
public class JwtUtils {

    /**
     * 创建一个密钥
     */

    private String secret = "Ljy991008X123435asdfSFS34wfsdfsdfSDSD32dfsddDDerQSNCK34SOWEK5354fdgdf4";

    SecretKey  key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));


    /**
     * 生成tocken
     */
    public String createTocken(Map<String,Object> map){
        String tocken = Jwts.builder()
                .setClaims(map)
                .issuedAt(new Date())
                .expiration(new Date(System.currentTimeMillis() * 30 * 60 * 1000))
                .signWith(key)
                .compact();
        return tocken;
    }

    /**
     * 解析tocken
     */
    public Claims parasTocken(String tocken){
        Jws<Claims> claimsJws = Jwts.parser()
                .verifyWith(key)
                .build()
                .parseSignedClaims(tocken);

        return claimsJws.getBody();
    }
}

三、通过jwt工具类将用户信息和权限生成tocken返回给前端

修改userServiceImpl中的代码

package com.ljy.myspringbootlogin.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ljy.myspringbootlogin.commont.Reuslt;
import com.ljy.myspringbootlogin.mapper.UserMapper;
import com.ljy.myspringbootlogin.model.UserModel;
import com.ljy.myspringbootlogin.service.IUserService;
import com.ljy.myspringbootlogin.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, UserModel> implements IUserService {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private JwtUtils jwtUtils;

    @Override
    public Reuslt<UserModel> login(String username, String password) {
        System.out.println("进入serviceimpl");
        System.out.println(username);
        System.out.println(password);
        //传入用户名和密码
        UsernamePasswordAuthenticationToken tocken = new UsernamePasswordAuthenticationToken(username, password);
        System.out.println("tocken"+tocken);
        //实现登录,此时就会调用loadUserByName
        //authenticate其实就是userdetails
        Authentication authenticate = null;
        try{
            authenticate= authenticationManager.authenticate(tocken);
            System.out.println("測試!!!!!");
        }catch (BadCredentialsException e){
            return Reuslt.error(500,"用户名或者密码错误");
        }
        UserModel principal = (UserModel)authenticate.getPrincipal();
        System.out.println("principal:"+principal);

        //生成tocken   修改这里就可以
        HashMap<String,Object> map = new HashMap<>();
        map.put("id",principal.getId());
        map.put("username",principal.getUsername());
        map.put("menuList",principal.getMenuList());
        map.put("roleList",principal.getRoleList());
        String tocken1 = jwtUtils.createTocken(map);
        System.out.println("tocken1"+tocken1);
        principal.setTocken(tocken1);
        return Reuslt.ok(principal);
    }
}

四、编写自定义过滤器(作用:当前端携带tocken访问别的接口的时候,需要进行判断是否有权限访问)

package com.ljy.myspringbootlogin.filter;

import com.ljy.myspringbootlogin.model.MenuModel;
import com.ljy.myspringbootlogin.model.RoleModel;
import com.ljy.myspringbootlogin.model.UserModel;
import com.ljy.myspringbootlogin.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 这个过滤器的作用:
 * 当某一个用户登录认证成功之后,后端给前端返回一个tocken信息,这个tocken信息里面包含着用户的属性和用户的角色、权限信息
 * 那么当这个用户再次访问别的接口的时候,后端就需要对前端传递过来的tocken信息进行解析,判断里面是否有权限访问接口
 *
 * 我们需要集成OncePerRequestFilter,这个是一个抽象类,其中有一个doFilter方法,在doFilter方法中调用了doFilterInternal方法,也是一个抽象方法,所以需要实现
 * 并且只会在请求之前执行一次
 *
 * 实现逻辑:
 * 1.获取前端传递过来的tocken
 * 2.解析tocken
 * 3.将解析出来的用户信息和权限使用Authentication告诉给springSecurity框架,springsecurity会将信息存储到SecurityContet中,
 * 从而放在SecurityContetHolder中
 * 4.有权限就访问,没有权限就报错
 *
 *
 * 注意:登录的时候,只需要放用户名和密码
 *      登录成功之后请求别的接口的时候,需要放的是用户信息和用户权限
 *
 */

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    JwtUtils jwtUtils;

    /**
     * 这个方法会被doFilter调用
     * @param request
     * @param response
     * @param filterChain
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {


        /**
         * 1.获取tocken
         * 因为前端将tocken信息放到了请求头中,所以我们需要使用request.getHeader("XXXX")来获取tocken
         */
        String tocken = request.getHeader("Authorization");
        if(tocken == null){
            doFilter(request,response,filterChain);
            return;
        }

        /**
         * 2.解析tocken
         * 使用我们自己编写的工具类,JwtUtils来解析
         */
        Claims claims = jwtUtils.parasTocken(tocken);
        System.out.println("解析出来的用户信息:"+claims);

        /**
         * 3.将解析出来的用户信息和权限使用Authentication告诉给springSecurity框架,springsecurity会将信息存储到SecurityContet中,
         * 从而放在SecurityContetHolder中
         */
        //3.1 从tocken中拿到信息
        Long id = claims.get("id", Long.class);
        String username = claims.get("username", String.class);
        List<String> menuList = claims.get("menuList", ArrayList.class);
        System.out.println("menuList:"+menuList);
        List<String> roleList = claims.get("roleList", ArrayList.class);
        //3.2 将信息放到userModel中
        UserModel userModel = new UserModel();
        userModel.setId(id);
        userModel.setUsername(username);
        userModel.setMenuList(menuList);
        //将权限信息转换成
        Collection<GrantedAuthority> grantedAuthorities = AuthorityUtils.createAuthorityList(menuList.toArray(new String[0]));
        userModel.setAuthorities(grantedAuthorities);
        System.out.println("userModel:"+userModel);
        //3.3 将信息当道SecurityContet中
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                new UsernamePasswordAuthenticationToken(userModel, "", userModel.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        System.out.println("userModel.getAuthorities():"+userModel.getAuthorities());
        //3.4 放行
        doFilter(request,response,filterChain);

    }
}

五、将自定义过滤器加载到springsecurity框架的过滤器链中
修改springsecurityConfig

package com.ljy.myspringbootlogin.config;

import com.ljy.myspringbootlogin.filter.JwtAuthenticationFilter;
import com.ljy.myspringbootlogin.springSecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

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

@EnableWebSecurity //开启springSecurity,会注册大量的过滤器链
@Configuration
@EnableMethodSecurity //开启方法级别的安全校验
public class springSecurityConfig {

    @Autowired
    private springSecurityService springSecurityService;
    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    /**
     * 配置过滤器链
     * @param http
     * @return
     * @throws Exception
     */

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
        http.csrf().disable();//跨域漏洞防御:关闭
        http.cors().disable();//跨域拦截关闭
        http.authorizeHttpRequests()
                .antMatchers("/user/**").permitAll()
                .anyRequest().authenticated();

        //将自己定义的过滤器添加到过滤器链中
        //将自定义过滤器放到认证过滤器之前
        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }


    /**
     * AuthenticationManager:负责认证,也就是认证规则
     * DaoAuthenticationProvider:负责将springSecurityService和passwordEncoder放进AuthenticationManager中
     * @return
     */

    @Bean
    public AuthenticationManager authenticationManager(){

        System.out.println("進入authenticationManager");
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(springSecurityService);
        //关联使用的密码编码器
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        //将daoAuthenticationProvider放到ProviderManager中
        ProviderManager providerManager = new ProviderManager(daoAuthenticationProvider);
        System.out.println("結束authenticationManager");
        return providerManager;

    }

    /**
     * 密码编码器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }
}

六、编写一个测试接口进行验证

package com.ljy.myspringbootlogin.controller;

import com.ljy.myspringbootlogin.commont.Reuslt;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {

    //创建登录控制器
    @RequestMapping("/test01")
    @PreAuthorize("hasAnyAuthority('qxgl')")  //需要的权限
    public Reuslt<String> aaa(){
        String a="测试";
        return Reuslt.ok(a);
    }

}

七、postman测试

1.登录生成tocken

2.访问测试接口,并写到上面的tocken

 

八、引入jwt依赖

<!--        引入jwt-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.12.6</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.12.6</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId> 
            <version>0.12.6</version>
            <scope>runtime</scope>
        </dependency>

 九、总结

到目前位置,我们实现了前端登录成功之后,通过数据库查询用户信息,并将用户信息生成tocken返回给前端,当前端携带tocken访问权限控制接口的时候,会判断是否有权限,进而判断是否可以访问。
但是存在一个问题,在上面的代码中,我们的接口权限是通过注解的方式写死的,在真实项目中,这个是绝对不可以的,所以我们需要动态实现权限,我们在下一章进行编写!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值