springboot基础搭建

springboot基础搭建

  • 版本说明
  • springboot基础搭建
    • 1. File->new->project
    • 2. 选择“Spring Initializr”,jdk选择8最好,或者默认
    • 3.补充项目信息
    • 4.勾选常用依赖也可以后期再pom 里添加
    • 5.包结构
    • 6.配置数据源
    • 创建第一个页面,并跳转到相应页面
  • 整合swagger
  • 整合shiro +JWT
    • 引入依赖
    • jwt 工具类编写
    • 封装token
    • 添加JWT的过滤器
    • 自定义Realm
    • 添加shiro配置文件

版本说明

spring boot:2.6.7
jdk:1.8

springboot基础搭建

1. File->new->project

2. 选择“Spring Initializr”,jdk选择8最好,或者默认

springboot基础搭建_第1张图片

3.补充项目信息

springboot基础搭建_第2张图片

4.勾选常用依赖也可以后期再pom 里添加

springboot基础搭建_第3张图片

5.包结构

springboot基础搭建_第4张图片

6.配置数据源

(我是把application.properties 改成了application.yml 纯属个人习惯)

server:
    port: 8080
spring:
    datasource:
        name: test
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://111.231.93.35:3306/ceshi?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: ljz
        password: Ljz217645!
        type: com.zaxxer.hikari.HikariDataSource
        hikari:
            minimum-idle: 5
            maximum-pool-size: 20
            auto-commit: true
            idle-timeout: 30000
            pool-name: zedHikariCP
            max-lifetime: 1800000
            connection-timeout: 30000
            connection-test-query: SELECT 1
mybatis:
    mapper-locations: classpath:mapper/*/*.xml
    configuration:
        map-underscore-to-camel-case: true
#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
    level:
        root: info
        com.zaxxer.hikari: warn
        com.zed.datamanager.dao: debug #SQL显示

创建第一个页面,并跳转到相应页面

springboot基础搭建_第5张图片
springboot基础搭建_第6张图片

整合swagger

这里我们采用swager3.0.0
1.引入依赖

		<properties>
	        <java.version>1.8</java.version>
	        <swagger.version>3.0.0</swagger.version>
	    </properties>
		<!-- swager -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>${swagger.version}</version>
        </dependency>

2.添加配置文件

package com.zhonggg.web.swagger.config;

import io.swagger.models.auth.In;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.*;

/**
 * 说明: swager 配置类
 *
 * @author ljz
 * @version 1.0
 * @date 2022/5/10 21:28
 */
@Configuration
public class SwaggerConfig {
	@Bean
	public Docket createRestApi() {
		return new Docket(DocumentationType.OAS_30)
			.apiInfo(apiInfo())
			.select()
			.apis(RequestHandlerSelectors.basePackage("com.zhonggg"))
			.paths(PathSelectors.any())
			.build();
	}

	private ApiInfo apiInfo() {
		return new ApiInfoBuilder()
			.title("Api Documentation")
			.contact(new Contact("", "", ""))
			.version("1.0")
			.build();
	}
	// 下面几个方法是为之后的token
	/**
	 * 设置授权信息
	 */
	private List<SecurityScheme> securitySchemes() {
		ApiKey apiKey = new ApiKey("token", "token", In.HEADER.toValue());
		return Collections.singletonList(apiKey);
	}

	/**
	 * 授权信息全局应用
	 */
	private List<SecurityContext> securityContexts() {
		return Collections.singletonList(
			SecurityContext.builder()
				.securityReferences(Collections.singletonList(new SecurityReference("token", new AuthorizationScope[]{new AuthorizationScope("global", "accessEverything")})))
				.build()
		);
	}

	@SafeVarargs
	private final <T> Set<T> newHashSet(T... ts) {
		if (ts.length > 0) {
			return new LinkedHashSet<>(Arrays.asList(ts));
		}
		return null;
	}
}

3.启动项目

springboot基础搭建_第7张图片
这个原因是这是因为Springfox使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X使用的是PathPatternMatcher。
解决方案在yml 文件中加入下面配置

spring:
    mvc:
        pathmatch:
            matching-strategy: ANT_PATH_MATCHER

启动后访问页面
http://localhost:8080/swagger-ui/index.html
springboot基础搭建_第8张图片

整合shiro +JWT

引入依赖

 <!--依赖版本-->
	<properties>
        <java.version>1.8</java.version>
        <swagger.version>3.0.0</swagger.version>
        <shiro.version>1.8.0</shiro.version>
        <jwt.version>3.2.0</jwt.version>
    </properties>
 <!--整合Shiro安全框架-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <!--集成jwt实现token认证-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>${jwt.version}</version>
        </dependency>

jwt 工具类编写

实现功能:

  • 生成token
  • 校验token是否合法
  • 通过载荷名字获取载荷的值
  • 通过token获取用户
package com.zhonggg.web.security.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;

import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Date;

/**
 * 说明: jwt 工具类用于token
 *
 * @author ljz
 * @version 1.0
 * @date 2022/5/13 14:43
 */
public class JwtUtils {
	private static final String   SECRET = "zhonggg";

	/**
	* 说明: 生成token
	* @param: * @param username
 	* @param password
	 * @param minute
	* @return: java.lang.String
	* @author ljz
	* @date: 2022/5/14 21:16
	*/

	public static String createToken(String username, String password,Integer minute ) throws UnsupportedEncodingException {
		Calendar nowTime = Calendar.getInstance();
		nowTime.add(Calendar.MINUTE, minute);
		Date date = nowTime.getTime();

		//密文生成
		String token = JWT.create()
			.withClaim("username", username)
			.withClaim("password", password)
			.withExpiresAt(date)
			.withIssuedAt(new Date())
			.sign(Algorithm.HMAC256(SECRET));
		return token;
	}

	/**
	* 说明:  校验token是否合法
	* @param: * @param token
	* @return: boolean
	* @author ljz
	* @date: 2022/5/14 21:17
	*/
	public static boolean verify(String token){
		try {
			JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
			verifier.verify(token);
			return true;
		} catch (UnsupportedEncodingException e) {
			return false;
		}
	}
	/**
	* 说明: 通过载荷名字获取载荷的值
	* @param: * @param token
	 * @param name
	* @return: java.lang.String
	* @author ljz
	* @date: 2022/5/14 21:18
	*/
	public static String getClaim(String token, String name){
		String claim = null;
		try {
			claim =  JWT.decode(token).getClaim(name).asString();
		}catch (Exception e) {
			return "getClaimFalse";
		}
		return claim;
	}

	public static String getUsername(String token){
		try {
			return getClaim(token,"username");
		} catch (Exception e) {
			return null;
		}

	}
}

封装token

封装token来替换Shiro原生Token,要实现AuthenticationToken接口
shiro默认supports的是UsernamePasswordToken,而我们现在采用了jwt的方式,所以这里我们自定义一个JwtToken,来完成shiro的supports方法。

package com.zhonggg.web.security.model;

import org.apache.shiro.authc.AuthenticationToken;
import org.springframework.stereotype.Component;

/**
 * 说明: TODO
 *
 * @author ljz
 * @version 1.0
 * @date 2022/5/14 21:20
 */
@Component
public class JwtToken implements AuthenticationToken {

	private String token ;

	public JwtToken() {
	}

	public JwtToken(String token) {
		this.token = token;
	}

	@Override
	public Object getPrincipal() {
		return token;
	}

	@Override
	public Object getCredentials() {
		return token;
	}
}

添加JWT的过滤器

这个过滤器是我们的重点,这里我们继承的是Shiro内置的BasicHttpAuthenticationFilter,一个可以内置了可以自动登录方法的的过滤器。
这个过滤器是要注册到shiro配置里面去的,用来辅助shiro进行过滤处理。所有的请求都会到过滤器来进行处理。
我们需要重写几个方法:

  • preHandle:拦截器的前置拦截,因为我们是前后端分析项目,项目中除了需要跨域全局配置之外,我们再拦截器中也需要提供跨域支持。这样,拦截器才不会在进入Controller之前就被限制了。
  • isAccessAllowed:是否允许访问。如果带有 token,则对 token 进行检查,否则直接通过。如果请求头不存在Token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true
  • isLoginAttempt:判断用户是否想要登入。检测 header 里面是否包含 Token 字段。
  • executeLogin:executeLogin实际上就是先调用createToken来获取token,这里我们重写了这个方法,就不会自动去调用createToken来获取token,然后调用getSubject方法来获取当前用户再调用login方法来实现登录,这也解释了我们为什么要自定义jwtToken,因为我们不再使用Shiro默认的UsernamePasswordToken了。
package com.zhonggg.web.security.filter;

import com.zhonggg.web.security.model.JwtToken;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;

/**
 * 说明: 拦截
 *
 * @author ljz
 * @version 1.0
 * @date 2022/5/14 21:40
 */
public class JwtFilter extends BasicHttpAuthenticationFilter {
	/**
	* 说明: 拦截器的前置  最先执行的 这里只做了一个跨域设置
	* @param: * @param request
	 * @param response
	* @return: boolean
	* @author ljz
	* @date: 2022/5/14 21:42
	*/
	protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
		System.out.println("JwtFilter -----> preHandle() 方法执行");
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse res = (HttpServletResponse) response;
		res.setHeader("Access-control-Allow-Origin", req.getHeader("Origin"));
		res.setHeader("Access-control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
		res.setHeader("Access-control-Allow-Headers", req.getHeader("Access-Control-Request-Headers"));
		// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
		if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
			res.setStatus(HttpStatus.OK.value());
			return false;
		}
		return super.preHandle(request, response);
	}

		/**
		 * preHandle 执行完之后会执行这个方法
		 * 再这个方法中 我们根据条件判断去去执行isLoginAttempt和executeLogin方法
		 * */
		protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
			System.out.println("JwtFilter -----> isAccessAllowed() 方法执行");
			/**
			 * 先去调用 isLoginAttempt方法 字面意思就是是否尝试登陆 如果为true
			 * 执行executeLogin方法
			 */
			if (isLoginAttempt(request,response)) {
				executeLogin(request, response);
				return true;
			}
			return true;
		}


		/**
		 * 这里我们只是简单去做一个判断请求头中的token信息是否为空
		 * 如果没有我们想要的请求头信息则直接返回false
		 * */
		@Override
		protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
			System.out.println( "JwtFilter -----> isLoginAttempt() 方法执行");
			HttpServletRequest req = (HttpServletRequest) request;
			String token = req.getHeader("token");
			return token != null;
		}


		/**
		 * 执行登陆
		 * 因为已经判断token不为空了,所以直接执行登陆逻辑
		 * 讲token放入JwtToken类中去
		 * 然后getSubject方法是调用到了MyRealm的 执行方法  因为上面我是抛错的所有最后做个异常捕获就好了
		 * */
		@Override
		protected boolean executeLogin(ServletRequest request, ServletResponse response) {
			System.out.println("JwtFilter -----> executeLogin() 方法执行");
			HttpServletRequest req = (HttpServletRequest) request;
			String token = req.getHeader("token");
			JwtToken jwtToken = new JwtToken(token);
			//然后交给自定义的realm对象去登陆, 如果错误他会抛出异常并且捕获

			System.out.println("-----执行登陆开始-----");
			getSubject(request, response).login(jwtToken);
			System.out.println("-----执行登陆结束----- 未抛出异常");
			return true;
		}
	/**
	 * 将非法请求跳转到 /unauthorized/**
	 */
	private void responseError(ServletResponse response, String message) {
		System.out.println("responseError");

		try {
			HttpServletResponse httpServletResponse = (HttpServletResponse) response;
			//设置编码,否则中文字符在重定向时会变为空字符串
			message = URLEncoder.encode(message, "UTF-8");
			httpServletResponse.sendRedirect("/unauthorized/" + message);
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}
}

自定义Realm

AccountRealm是shiro进行登录或者权限校验的逻辑所在,算是核心了,我们需要重写3个方法,分别是

  • supports:为了让realm支持jwt的凭证校验
  • doGetAuthorizationInfo:权限校验
  • doGetAuthenticationInfo:登录认证校验
package com.zhonggg.web.security.model;

import cn.hutool.core.util.StrUtil;
import com.zhonggg.web.security.model.JwtToken;
import com.zhonggg.web.security.utils.JwtUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;

/**
 * 说明: 自定义Realm
 *
 * @author ljz
 * @version 1.0
 * @date 2022/5/14 21:28
 */
@Component
public class MyRealm extends AuthorizingRealm {
	@Override
	public boolean supports(AuthenticationToken token) {
		return token instanceof JwtToken;
	}
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
		System.out.println("MyRealm doGetAuthorizationInfo() 方法授权 ");
		String token = principalCollection.toString();
		String username = JwtUtils.getClaim(token,"username");
		if (StrUtil.isBlank(username)) {
			throw new AuthenticationException("token认证失败");
		}
		//TODO
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		//其实这里应该是查询当前用户的角色或者权限去的,意思就是将当前用户设置一个svip和vip角色
		//权限设置一级权限和耳机权限 正常来说应该是去读取数据库只添加当前用户的角色权限的
		info.addRole("vip");
		info.addRole("svip");
		info.addStringPermission("一级权限");
		info.addStringPermission("二级权限");
		System.out.println("方法结束咯-------》》》");

		return info;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
		System.out.println("认证-----------》》》》");
		System.out.println("1.toString ------>>> " + authenticationToken.toString());
		System.out.println("2.getCredentials ------>>> " + authenticationToken.getCredentials().toString());
		System.out.println("3. -------------》》 " +authenticationToken.getPrincipal().toString());
		String jwt = (String) authenticationToken.getCredentials();
		String username= null;
		//decode时候出错,可能是token的长度和规定好的不一样了
		try {
			username= JwtUtils.getUsername(jwt);
		}catch (Exception e){
			throw new AuthenticationException("token非法,不是规范的token,可能被篡改了,或者过期了");
		}
		if (!JwtUtils.verify(jwt)||username==null){
			throw new AuthenticationException("token认证失效,token错误或者过期,重新登陆");
		}

		/*User user=userService.getUser(username);
		if (user==null){
			throw new AuthenticationException("该用户不存在");
		}*/

		return new SimpleAuthenticationInfo(jwt, jwt, "MyRealm");
	}
}

添加shiro配置文件

配置文件实现:

  • 创建defaultWebSecurityManagerBean对象
  • 创建ShiroFilterFactoryBean来进行
  • 过滤拦截,权限和登录
  • 关闭session
  • 添加注解权限开发

springBoot整合jwt与单纯的shiro实现认证有三个不一样的地方

  • 因为不适用Session,所以为了防止会调用getSession()方法而产生错误,需要关闭session
  • 关闭SHiroDao等
  • 注册JwtFilter到ShiroFilterFactoryBea中
package com.zhonggg.web.security.config;

import com.zhonggg.web.security.model.MyRealm;
import com.zhonggg.web.security.filter.JwtFilter;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 说明: shiro 配置
 *
 * @author ljz
 * @version 1.0
 * @date 2022/5/14 21:51
 */
@Configuration
public class ShiroConfig {

	@Bean(name = "securityManager")
	public DefaultWebSecurityManager securityManager(MyRealm myRealm){
		DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
		// 设置自定义 realm.
		securityManager.setRealm(myRealm);

		//关闭session
		DefaultSubjectDAO subjectDAO=new DefaultSubjectDAO();
		DefaultSessionStorageEvaluator sessionStorageEvaluator=new DefaultSessionStorageEvaluator();
		sessionStorageEvaluator.setSessionStorageEnabled(false);
		subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator);
		securityManager.setSubjectDAO(subjectDAO);
		return securityManager;
	}

	/**
	 * 先走 filter ,然后 filter 如果检测到请求头存在 token,则用 token 去 login,走 Realm 去验证
	 */
	@Bean
	public ShiroFilterFactoryBean factory(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
		ShiroFilterFactoryBean factoryBean=new ShiroFilterFactoryBean();
		factoryBean.setSecurityManager(securityManager);
		// 添加自己的过滤器并且取名为jwt
		Map<String, Filter> filterMap=new LinkedHashMap<>();
		//设置我们自定义的JWT过滤器
		filterMap.put("jwt",new JwtFilter());
		factoryBean.setFilters(filterMap);

		// 设置无权限时跳转的 url;
		factoryBean.setUnauthorizedUrl("/unauthorized/无权限");
		Map<String,String>filterRuleMap=new HashMap<>();
		// 所有请求通过我们自己的JWT Filter
		filterRuleMap.put("/**","jwt");
		// 访问 /unauthorized/** 不通过JWTFilter
		filterRuleMap.put("/unauthorized/**","anon");
		factoryBean.setFilterChainDefinitionMap(filterRuleMap);
		return factoryBean;
	}

	/**
	 * 添加注解支持,如果不加的话很有可能注解失效
	 */
	@Bean
	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){

		DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
		defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
		return defaultAdvisorAutoProxyCreator;
	}

	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){

		AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
		advisor.setSecurityManager(securityManager);
		return advisor;
	}

	@Bean
	public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}
}

你可能感兴趣的:(jwt,spring,boot,shiro,JWT)