Springboot整合shiro+jwt---实现单点登录,权限认证和控制+实现代码

shiro用来认证用户及权限控制,jwt用来生成一个token,暂存用户信息。jwt生成一个token存储在客户端,每次请求将其存储在header中,解决了跨域,可以通过自定义的方法进行验证,解决了分布式验证的问题。

大体包含哪些类

(1)LoginAction.java (controller层)。
(2)JwtUtil.java(工具类):实现了利用登陆信息生成token,更新token,根据token获取username,token验证等方法。
(3)JWTFilter.java(过滤器):拦截除登陆、注册以外的所有请求。
(4)LoginRealm.java : 实现了登陆认证,和权限获取的方法。
(5)ShiroConfig.java(配置类) :用于生成ShiroManage及将shiroRealm付给ShiroManage,并将jwtFilter添加进shiro的拦截器链中(也就是配置了哪些请求会被拦截,哪些不会)。
(6)ResponseBean.java : 用于统一格式,也就是响应给客户端的格式。
(7)JWTToken.java : JWTFilter要登录令牌LoginRealm时候,封装的一个参数(不能直接传String)。

大体结构如下:
Springboot整合shiro+jwt---实现单点登录,权限认证和控制+实现代码_第1张图片

后端大体流程:

  1. 用户使用username和password登陆,对password进行加密,通过username查询库中是否有该条记录,并比较加密后的密码是否相同(先只比较用户名,用户名成功再去比较密码),登陆成功后利用JwtUtil生成带过期时间的token,以后发送请求时都需要在header中添加Authorization字段附加该token信息;
  2. 结合程序实现一个JwtUtil,在其中实现利用登陆信息生成token,根据token获取username,token验证等方法;
  3. 实现一个JWTFilter继承BasicHttpAuthenticationFilter类,该拦截器需要拦截所有请求除(除登陆、注册等请求),用于判断请求是否带有token,并获取token的值传递给shiro的登陆认证方法作为参数,用于获取token;
  4. 定义LoginRealm继承AuthorizingRealm类,在其中实现登陆验证及权限获取的方法;
  5. 定义ShiroConfig配置类,用于生成ShiroManage及将shiroRealm付给ShiroManage,并配置请求拦截规则;

代码实现

前端+后台代码见如下链接
百度网盘链接:
链接:https://pan.baidu.com/s/1QRucTecIWVZsAmuqg6sEdA
提取码:wyu2

登陆部分代码如下:
1.登陆的js页面(使用layui前后端分离)



	
		
		
	
	
		

登录

后端
2.LoginAction.java

第一次登录:前端传用户名和密码到后台,先判断数据库里是否存在,存在则再比较用户的密码是否正确,密码正确则登录成功,生成一个带过期时间的token令牌发送给客户端(下面代码省略了这个过程直接通过比较用户名和密码是否相同来判断是否登录成功)

package com.action;
import com.token.JwtUtil;
import com.vo.ResponseBean;
import jdk.nashorn.internal.parser.Token;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
@RestController
@CrossOrigin("*")
@RequestMapping("/api/")
public class LoginAction {
    @RequestMapping("/login")
    public ResponseBean login(String username, String password, HttpServletResponse response) {
       /* System.out.println("username=="+username);
        System.out.println("password=="+password);*/
        if (username != null && username.equals(password)) {
            //登录成功,产生令牌,发送给客户端
            String token = JwtUtil.generToken(username,"","");
            System.out.println("action.token"+token);
            //令牌域名(头子跨域)
            response.addHeader("Access-Control-Expose-Headers","token");
            response.addHeader("token", token);
            return new ResponseBean(200, 0, null);
        } else {
            return new ResponseBean(500, 0, null);
        }
    }
}

3.JWTFilter.java
再一次发起请求时,由JWTFilter.java对请求进行拦截。拦截的规则在ShrioConfig中配置
先得到客户端传过来的令牌,再对令牌进行更新,更新成功进入到isAccessAllowed方法进行登录判断和权限检查,登录成功而且用于权限则返回ture,否则返回false进入到onAccessDenied方法报401错误

package com.token;
import org.apache.shiro.authz.AuthorizationException;
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;

/**
 *   preHandle-->isAccessAllowed(true/false)-->executeLogin(是否有异常)-->LoginRealm
 *      登录成功则访问资源
 *      登录失败则onAccessDenied(这个方法返回了一个401)
 * */
public class JWTFilter extends BasicHttpAuthenticationFilter {
    /**
     * 1,这个方法一般用于处理跨域
     * 2.更新令牌
     * 3.发送令牌给客户端
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.addHeader("Access-Control-Allow-Origin", "*");
        httpServletResponse.addHeader("Access-Control-Allow-Headers", "*");
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE");
        httpServletResponse.addHeader("Access-Control-Expose-Headers", "token");

        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }

        //得到客户端传过来的令牌
        String token = ((HttpServletRequest) request).getHeader("token");
        System.out.println("Filter.token=="+token);

        //更新令牌,更新失败返回"0"
        String newToken = JwtUtil.updateToken(token);
        System.out.println("new.token=="+newToken);

        if ("0".equals(newToken) == false) {
            httpServletResponse.addHeader("token", newToken);
        }
        //返回true则通过过滤器
        System.out.println("super.preHandle(request, response)=="+super.preHandle(request, response));
        return super.preHandle(request, response);
    }
    
    /**
     * 如果方法返回true,则登录成功,且有授权
     * 返回false则执行下面onAccessDenied
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //登录和检查权限都会交给Loginrealm进行。
        try {
            //登录 
            executeLogin(request, response);
            // 检查权限
            String url = ((HttpServletRequest) request).getRequestURI();
            getSubject(request, response).checkPermission(url);
        } catch (Exception e) {
            //e.printStackTrace();
            return false; //onAccessDenied
        }
        return true;
    }
    /**
     *登录
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String authorization = httpServletRequest.getHeader("token");
        JWTToken token = new JWTToken(authorization);
        // 提交给realm进行登入,如果错误他会抛出异常并被捕获
        getSubject(request, response).login(token);
        // 如果没有抛出异常则代表登入成功,返回true
        return true;
    }
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse r = (HttpServletResponse) response;
        r.sendError(401, "HttpServletResponse.SC_UNAUTHORIZED");
        return false;
    }
}

4.JwtUtil.java

package com.token;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
@Component
public class JwtUtil {
    //密码,绝对保密,应当写在配置文件中,这里为了方便直接写了
    public static String sercetKey = "mingtianhenganghao";
    public final static long keeptime = 18000000;

   /* @Value("${token.sercetKey}")
    public  static String sercetKey;
    @Value("${token.keeptime}")
    public static long keeptime;
*/
   /*
   *
   * */
    public static void main(String[] args) {
        String token = JwtUtil.generToken("admin", null,null);
        System.out.println("sercetKey=="+sercetKey);
        System.out.println("keeptime=="+keeptime);
      //  String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhZG1pbiIsImlhdCI6MTU3MzM1MTAzNiwiZXhwIjoxNTczMzUxMDQxfQ.6W8UpzhLMKA794zuwnN2a1aFA3Wpu2yVz976ewhhuUk";
        String username = JwtUtil.getUsername(token);
        System.out.println(username);
        System.out.println("JwtUtil.token"+token);
    }

    /**
     * 产生令牌
     * @param id  用户名
     * @param issuer  签发者
     * @param subject 主体(内容)
     * @return
     */
    public static String generToken(String id, String issuer, String subject) {
        long ttlMillis = keeptime;
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(sercetKey);
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

        JwtBuilder builder = Jwts.builder().setId(id)
                .setIssuedAt(now);
        if (subject != null) {
            builder.setSubject(subject);
        }
        if (issuer != null) {
            builder.setIssuer(issuer);
        }
        builder.signWith(signatureAlgorithm, signingKey);

        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp);
        }
        System.out.println("builder.compact()=="+builder.compact());
        return builder.compact();
    }

    /**
     * 更新令牌
     * @param token
     * @return
     */
    public static String updateToken(String token) {
        try {
            Claims claims = verifyToken(token);
            String id = claims.getId();
            String subject = claims.getSubject();
            String issuer = claims.getIssuer();
            Date date = claims.getExpiration();
            return generToken(id, issuer, subject);
        } catch (Exception ex) {
            //  ex.printStackTrace();
        }
        return "0";
    }

    /**
     * 获取用户名
     * @param token
     * @return
     */
    public static String getUsername(String token) {

        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(DatatypeConverter.parseBase64Binary(sercetKey))
                    .parseClaimsJws(token).getBody();
            return claims.getId();
        } catch (Exception e) {
            //e.printStackTrace();
        }
        return null;
    }

    public static Claims verifyToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(DatatypeConverter.parseBase64Binary(sercetKey))
                .parseClaimsJws(token).getBody();
        return claims;
    }
}

5.LoginRealm.java

package com.token;
import io.jsonwebtoken.Claims;
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;
import java.util.logging.Logger;
/**
 *  令牌登录或权限类
 */

@Component
public class LoginRealm extends AuthorizingRealm {

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }


    /**
     * 管理权限
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //得登录的用户名
        String username = JwtUtil.getUsername(principals.toString());
        Logger.getLogger("ROOT").info("username:"+username);
        //查数据库,用户的权限,略
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //把当前登录的用户有哪些权限添加进来,添加哪些,就能访问哪些网址
        authorizationInfo.addStringPermission("/api/searchDep");
        //authorizationInfo.addStringPermission("/api/removeDepById");
        //authorizationInfo.addStringPermissions();
        return authorizationInfo;
    }

    /**
     * 管理登录  如果有异常,则令牌无效
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //得到客户端传过来的令牌
        String tokenString =(String) token.getPrincipal();
        //根据令牌得用户名,可以查用户库
        String username = JwtUtil.getUsername(tokenString);
        if (username==null)
        {
            throw new RuntimeException("登录失败");
        }
        return new SimpleAuthenticationInfo(tokenString, tokenString, getName());
    }
}

6.ShiroConfig.java(配置类)

package com.token;
import org.apache.commons.collections.map.LinkedMap;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;

@Component
public class ShiroConfig {

    @Autowired
    private LoginRealm loginRealm;
    /**
     * 使用哪种类型的
     * 安全管理器
     *
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        /*
         * 关闭shiro自带的session,详情见文档
         * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);

        // 设置自定义 realm.
        securityManager.setRealm(loginRealm);
        return securityManager;
    }
    /**
     * 配置一个拦截规则
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean factory(SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);

        Map filterMap = new HashMap();
        factoryBean.setFilters(filterMap);

        //设置我们自定义的JWT过滤器
        filterMap.put("jwt", new JWTFilter());
        Map m = new LinkedMap();
        m.put("/api/login", "anon"); //不经过我们的过滤器
        m.put("/**", "jwt,authc"); //其他的经过我们的过滤器,且必须要认证
        factoryBean.setFilterChainDefinitionMap(m);
        return factoryBean;
    }
}

7.ResponseBean .java

package com.vo;
public class ResponseBean {
    private long code;
    private long count;
    private Object data;
    public long getCode() {
        return code;
    }
    public void setCode(long code) {
        this.code = code;
    }
    public long getCount() {
        return count;
    }

    public void setCount(long count) {
        this.count = count;
    }
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    public ResponseBean(long code, long count, Object data) {
        this.code = code;
        this.count = count;
        this.data = data;
    }
}

8.token.java

package com.token;

import org.apache.shiro.authc.AuthenticationToken;

/**
 * JWTFilter要登录令牌LoginRealm时候,封装的一个参数
 * 不能直接传String
 */
public class JWTToken implements AuthenticationToken {
    private  String token;
    public JWTToken(String token)
    {
        this.token = token;
    }

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

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

你可能感兴趣的:(java,java,后端)