SpringBoot实现token验证,JWT、shiro的使用

  • Post author:
  • Post category:其他


JWT简单说明:

JWT由单短信息构成:

header(base64加密得到第一部分)、playload(base64加密得到第二部分)、signature(将前两部分加密的字符串用 . 号拼接u 拼接,再yong hs256加秘加盐,然后再base64加密,得到第三部分);最后将三部分用 .号连接。

header:由令牌的类型(即JWT)和正在使用过的签名算法组成。如:


{


"alg": "HS256",


"typ": "JWT"


}

playload:


预定义载荷


{


"sub": "1",


"iss": "http://localhost:8000/auth/login",


"iat": 1451888119,

​​​​​​​

"exp": 1454516119,

​​​​​​​

"nbf": 1451888119,

​​​​​​​

"jti": "37c107e4609ddbcc9c096ea5ee76c667",

​​​​​​​

"aud": "dev"

​​​​​​​

}

这里面的前 7 个字段都是由官方所定义的,也就是

预定义

(Registered claims)的,并不都是必需的。

iss (issuer):签发人

sub (subject):主题

aud (audience):受众

exp (expiration time):过期时间

nbf (Not Before):生效时间,在此之前是无效的

iat (Issued At):签发时间

jti (JWT ID):编号


公有的载荷

在使用 JWT 时可以额外定义的载荷。为了避免冲突,应该使用

IANA JSON Web Token Registry

中定义好的,或者给额外载荷加上类似命名空间的唯一标识。


私有载荷

在信息交互的双方之间约定好的,既不是预定义载荷也不是公有载荷的一类载荷。

**验证流程:

用户验证成功后会返回给浏览器客户端一个token字符串,服务端不做保存,下次再请求的时候,会携带token;

超时验证-》token合法性验证(前两部分加密对比)-〉签名验证

token只保存在前端,后端只负责校验,不可一修改token,只要一改就认证失败,token签发后,不能手动使其失效,

如果服务器重启,token将失效,需要重新登录。

vue的代码,设置请求头携带toke传递给后台;

//设置请求拦截器,把token传递到后台
axios.interceptors.request.use(function(config){
	// console.log(config.url)
	//打开进度条
	// NProgress.start()
	config.headers.Authorization=window.sessionStorage.getItem('token');
	return config;
},function(err){
 
})

springboot后台代码:

pom.xml文件中导入依赖:

   <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>
创建签名和验证签名的工具类:

@Data
//@TableName实体类的类名和数据库表名不一致
//@TableId实体类的主键名称和表中主键名称不一致,主键策略type
//@TableField实体类中的成员名称和表中字段名称不一致,value是数据库字段名,fill是自动插入、更新等
@TableName("User")
public class User {
	//主键策略
	@TableId(type=IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String token;

@TableField(value="create_time",fill = FieldFill.INSERT)
private Date createTime;
@TableField(value="update_time",fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

}



import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.entity.User;

public class TokenUtil {
	 private static final long EXPIRE_TIME= 15*60*1000;
	    private static final String TOKEN_SECRET="token123";  //密钥盐


	    /**
	     * 签名生成
	     * @param user
	     * @return
	     */
	    public static String sign(User user){

	        String token = null;
	        try {
	            Date expiresAt = new Date(System.currentTimeMillis() + EXPIRE_TIME);
	            token = JWT.create()
	                    .withIssuer("auth0")
	                    .withClaim("username", user.getName())
	                    .withExpiresAt(expiresAt)
	                    // 使用了HMAC256加密算法。
	                    .sign(Algorithm.HMAC256(TOKEN_SECRET));
	        } catch (Exception e){
	            e.printStackTrace();
	        }
	        return token;

	    }


	    /**
	     * 签名验证
	     * @param token
	     * @return
	     */
	    public static boolean verify(String token){


	        try {
	            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer("auth0").build();
	            DecodedJWT jwt = verifier.verify(token);
	            System.out.println("认证通过:");
	            System.out.println("issuer: " + jwt.getIssuer());
	            System.out.println("username: " + jwt.getClaim("username").asString());
	            System.out.println("过期时间:      " + jwt.getExpiresAt());
	            return true;
	        } catch (Exception e){
	            return false;
	        }

	    }
}
拦截器验证token:

import java.io.PrintWriter;

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

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import com.example.utils.TokenUtil;

import net.sf.json.JSONObject;
//拦截器
@Component
public class TokenInterceptor implements HandlerInterceptor{
	 @Override
	    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler)throws Exception{

	        if(request.getMethod().equals("OPTIONS")){
	            response.setStatus(HttpServletResponse.SC_OK);
	            return true;
	        }

	        response.setCharacterEncoding("utf-8");

	        String token = request.getHeader("Authorization");
	    System.out.println("token是"+token);
	        if(token != null){
	            boolean result = TokenUtil.verify(token);//验证token
	            if(result){
	                System.out.println("通过拦截器");
	                return true;
	            }
	        }
	        response.setCharacterEncoding("UTF-8");
	        response.setContentType("application/json; charset=utf-8");
	        PrintWriter out = null;
	        try{
	            JSONObject json = new JSONObject();
	            json.put("success","false");
	            json.put("msg","认证失败,未通过拦截器");
	            json.put("code","50000");
	            response.getWriter().append(json.toString());
	            System.out.println("认证失败,未通过拦截器");
	                    response.getWriter().write("50000");
	        }catch (Exception e){
	            e.printStackTrace();
	            response.sendError(500);
	            return false;
	        }


	        return false;

	    }
}
拦截器配置类:

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.example.handler.TokenInterceptor;

/**
 * 拦截器配置
 */
@Configuration
public class IntercepterConfig implements WebMvcConfigurer{
	  private TokenInterceptor tokenInterceptor;

	    //构造方法
	    public IntercepterConfig(TokenInterceptor tokenInterceptor){
	        this.tokenInterceptor = tokenInterceptor;
	    }

	    @Override
	    public void addInterceptors(InterceptorRegistry registry){
	        List<String> excludePath = new ArrayList<>();
	        excludePath.add("/user_register"); //注册
	        excludePath.add("/login"); //登录
	        excludePath.add("/logout"); //登出
	        excludePath.add("/static/**");  //静态资源
	        excludePath.add("/assets/**");  //静态资源

	        registry.addInterceptor(tokenInterceptor)
	                .addPathPatterns("/**")
	                .excludePathPatterns(excludePath);
	        WebMvcConfigurer.super.addInterceptors(registry);

	    }
}
登录成功后创建token传递给前端:

@RequestMapping("/login")
//	@ResponseBody
	public R login(@RequestBody Map<String, String> person) {	
		 
		User user=new User();
		
		if(person.get("username").equals("lambo")) {
			user.setAge(18);
			user.setName("lmabo");
			user.setId(11L);
			//登录成功后创建token传递给客户端
			String token = TokenUtil.sign(user);
			user.setToken(token);
			System.out.println("username is:"+person.get("username"));
			return R.ok().data("myinfo",user);
		}else {
			System.out.println("错误is:"+person.get("username"));
			return R.error();
		}
	}

****shiro


https://www.w3cschool.cn/shiro/co4m1if2.html



shiro

.apache.org/


http://shiro.apache.org/10-minute-tutorial.html

提供了认证、授权、加密、会话管理,与spring Security 一样都是做一个权限的安全框架;

记住一点,Shiro


不会去维护用户、维护权限;这些需要我们自己去设计/


提供;然后通过相应的接口注入给Shiro


即可。

认证简单的说,就是登录的时候判断你的用户名和密码是否完全匹配,就是证明你是你。



授权,是在认证的基础之上,进行角色和权限的授予。权限决定了一个用户可以进行怎样的操作。




角色、权限



权限定义了一个用户是否可以执行某个操作。



角色就是一组权限的集合。

1. Subject:主体,一般指用户。

2. SecurityManager:安全管理器,管理所有Subject,可以配合内部安全组件。(类似于SpringMVC中的DispatcherServlet)

3. Realms:用于进行权限信息的验证,一般需要自己实现。

详细功能:

1. Authentication:身份认证/登录(账号密码验证)。

2. Authorization:授权,即角色或者权限验证。

3. Session Manager:会话管理,用户登录后的session相关管理。

4. Cryptography:加密,密码加密等。

5. Web Support:Web支持,集成Web环境。

6. Caching:缓存,用户信息、角色、权限等缓存到如redis等缓存中。

7. Concurrency:多线程并发验证,在一个线程中开启另一个线程,可以把权限自动传播过去。

8. Testing:测试支持;

9. Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。

10. Remember Me:记住我,登录后,下次再来的话不用登录了。

使用步骤:

1.导入jar包:

<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>

2.shiro配置类,当中配置拦截器,决定哪些url需要验证,哪些角色需要验证

import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShiroConfig {
	 //将自己的验证方式加入容器
    @Bean
    public CustomRealm myShiroRealm() {
        CustomRealm customRealm = new CustomRealm();
        return customRealm;
    }

    //权限管理,配置主要是Realm的管理认证
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

    //Filter工厂,设置对应的过滤条件和跳转条件,拦截需要安全控制的URL,然后进行相应的控制,其中authc指定需要认证的uri,anon指定排除认证的uri
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> map = new HashMap<>();
        //登出
        map.put("/logout", "logout");
        //对所有用户认证
        map.put("/**", "authc");
        //不认证的url
        map.put("/login", "anon");
 

3.自定义realm

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.authc.credential.CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import com.example.entity.User;

public class CustomRealm  extends AuthorizingRealm{
	 //数据库存储的用户密码的加密salt,正式环境不能放在源代码里
    private static final String encryptSalt = "jwt";
	
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		
		//加这一步的目的是在Post请求的时候会先进认证,然后在到请求
        if (token.getPrincipal() == null) {
        	
            return null;
        }
        //获取用户信息,token中的sername
        String username = token.getPrincipal().toString();
       
        //根据token中username到数据库中查询加密过的密码
        String encrypassword="";
        //获取数据库中加密的盐
        
        User user = new User();
        if (user == null) {
            //这里返回后会报出对应异常
        	
            return null;
        } else {
            //这里验证authenticationToken和simpleAuthenticationInfo的信息
        	//第一个参数:token中的username,第二个参数:数据库中加密的密码,第三个参数:加密的盐,第四个参数:reaml对象,
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, encrypassword, ByteSource.Util.bytes(encryptSalt), getName());
            return simpleAuthenticationInfo;
        }
     
	}
	
	   
    /**
     * 设置自定义认证加密方式
     *
     * @param credentialsMatcher 默认加密方式
     */
    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
    		//自定义认证加密方式
        CustomCredentialsMatcher customCredentialsMatcher = new CustomCredentialsMatcher();
        // 设置自定义认证加密方式
        super.setCredentialsMatcher(customCredentialsMatcher);
    }
}

4.开始配置凭证匹配器,可以用shiro.ini文件,也可以自定义一个匹配器类;

数据库中的密码是加密加盐后的密码,所以我们给realm配置了加密算法的规则,让它将我们传过去的密码进行了同样的加密加盐(这里盐不需要我们设置,是从数据库中查询出来的),然后再和数据库的数据进行比对认证.

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;

/**
 * 自定义认证加密方式,这里的加密方式要和注册时密码的加密方式一致
 */
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
    @Override
    public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) {
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
      // 获得用户输入的密码:(可以采用加盐(salt)的方式去检验)
        String inPassword = new String(((UsernamePasswordToken) authcToken).getPassword());
        String username = ((UsernamePasswordToken) authcToken).getUsername();
        //获得数据库中的密码
        String dbPassword = (String) info.getCredentials();
        SimpleAuthenticationInfo saInfo = (SimpleAuthenticationInfo)info;
      //  ByteSource salt = saInfo.getCredentialsSalt();
        ByteSource salt = ByteSource.Util.bytes(username);

        //加密类型,密码,盐值,加密次数
        Object tokenCredentials = new SimpleHash("md5", inPassword, username, 2).toHex();
        //数据库存储密码
        Object accountCredentials = getCredentials(info);
        //将密码加密与系统加密后的密码校验,内容一致就返回true,不一致就返回false
        return equals(tokenCredentials, accountCredentials);
    }
}

5.在登录的控制器中:

//带报错的数据
    @RequiresRoles("client")
	@RequestMapping("/login")
//	@ResponseBody
	public R login(@RequestBody Map<String, String> person) {	
    	Subject subject = SecurityUtils.getSubject();
        try {
            //将用户请求参数封装后,直接提交给Shiro处理
            UsernamePasswordToken token = new UsernamePasswordToken(person.get("username"), person.get("password"));
            subject.login(token);
            System.out.println("认证成功");
        } catch (UnknownAccountException uae) {
        	System.out.println("账户不存在");
        } catch (IncorrectCredentialsException ice) {
        	System.out.println("密码不正确");
        } catch (LockedAccountException lae) {
        	System.out.println("用户被锁定了 ");
        } catch (AuthenticationException ae) {
            //无法判断是什么错了
        	System.out.println("未知错误");
        }

		return R.ok().data("myinfo",1);
}



版权声明:本文为u011146511原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。