一、说明
前面文章我们写到了通过数据库查询出用户信息并返回,那么在真实的项目中呢,后端是需要给前端返回一个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 {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate JwtUtils jwtUtils;@Overridepublic 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其实就是userdetailsAuthentication 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 {@AutowiredJwtUtils jwtUtils;/*** 这个方法会被doFilter调用* @param request* @param response* @param filterChain* @throws ServletException* @throws IOException*/@Overrideprotected 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 {@Autowiredprivate springSecurityService springSecurityService;@Autowiredprivate JwtAuthenticationFilter jwtAuthenticationFilter;/*** 配置过滤器链* @param http* @return* @throws Exception*/@Beanpublic 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*/@Beanpublic 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*/@Beanpublic 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访问权限控制接口的时候,会判断是否有权限,进而判断是否可以访问。
但是存在一个问题,在上面的代码中,我们的接口权限是通过注解的方式写死的,在真实项目中,这个是绝对不可以的,所以我们需要动态实现权限,我们在下一章进行编写!