springboot+shiro+jwt+vue配置全攻略

瓜子妹發表於2020-10-10
一、後端配置
0.pom.xml

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.11.0</version>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

1.shiro配置檔案
ShiroConfiguration.java
@Slf4j
@Configuration
public class ShiroConfiguration {

    /**
     * shiro 安全過濾鏈
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

//        shiroFilterFactoryBean.setUnauthorizedUrl("/login");

        // 新增自己的過濾器並且取名為jwt
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("jwt", new JWTFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setUnauthorizedUrl("/401");

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 所有的請求通過我們自己的JWT filter
        filterChainDefinitionMap.put("/**", "jwt");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * web 安全管理器
     * @return
     */
    @Bean(name = "securityManager")
    public DefaultSecurityManager getDefaultSecurityManager(@Qualifier("myRelam") MyRealm myRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        return securityManager;
    }
    @Bean(name="myRelam")
    public MyRealm getMyRealm() {
        return new MyRealm();
    }

    /**
     * 下面的程式碼是新增註解支援
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 強制使用cglib,防止重複代理和可能引起代理出錯的問題
        // https://zhuanlan.zhihu.com/p/29161098
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

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

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

2.jwt過濾器 

JWTFilter.jav
import com.fasterxml.jackson.databind.ObjectMapper;
import com.szht.cbhs.core.http.Result;
import lombok.extern.slf4j.Slf4j;
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;

@Slf4j
public class JWTFilter extends BasicHttpAuthenticationFilter {

	/**
     * 判斷使用者是否想要登入。
     * 檢測header裡面是否包含Authorization欄位即可
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String authorization = req.getHeader("Authorization");
        log.info("判斷使用者是否想要登入:{}",authorization);
        return authorization != null;
    }

    /**
     *
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String authorization = httpServletRequest.getHeader("Authorization");
        log.info("判斷使用者是否想要登入x:{}",authorization);
        JWTToken token = new JWTToken(authorization);
        // 提交給realm進行登入,如果錯誤他會丟擲異常並被捕獲
        try {
            getSubject(request, response).login(token);
            // 如果沒有丟擲異常則代表登入成功,返回true
        }catch (Exception e){
            return false;
        }
        return true;
    }

    /**
     * 這裡我們詳細說明下為什麼最終返回的都是true,即允許訪問
     * 例如我們提供一個地址 GET /article
     * 登入使用者和遊客看到的內容是不同的
     * 如果在這裡返回了false,請求會被直接攔截,使用者看不到任何東西
     * 所以我們在這裡返回true,Controller中可以通過 subject.isAuthenticated() 來判斷使用者是否登入
     * 如果有些資源只有登入使用者才能訪問,我們只需要在方法上面加上 @RequiresAuthentication 註解即可
     * 但是這樣做有一個缺點,就是不能夠對GET,POST等請求進行分別過濾鑑權(因為我們重寫了官方的方法),但實際上對應用影響不大
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        System.out.println("isAccessAllowed方法");
        try{
            return executeLogin(request,response);
        }catch (Exception e){
            System.out.println("錯誤"+e);
//            throw new ShiroException(e.getMessage());
            responseError(response,"shiro fail");
            return false;
        }
//        if (isLoginAttempt(request, response)) {
//            try {
//                executeLogin(request, response);
//            } catch (Exception e) {
//                response401(request, response);
//            }
//        }
//        return true;
    }

    /**
     * 對跨域提供支援
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域時會首先傳送一個option請求,這裡我們給option請求直接返回正常狀態
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 將非法請求跳轉到 /401
     */
    private void response401(ServletRequest req, ServletResponse resp) {
        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) resp;
            httpServletResponse.sendRedirect("/401");
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    private void responseError(ServletResponse response,String msg){

        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setStatus(401);
        httpResponse.setCharacterEncoding("UTF-8");
        httpResponse.setContentType("application/json;charset=UTF-8");
        try {
            String rj = "shiro shibai";
            httpResponse.getWriter().append(rj);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.

JWTToken.java
import org.apache.shiro.authc.AuthenticationToken;


public class JWTToken implements AuthenticationToken {
	
	private static final long serialVersionUID = 1L;
	// 祕鑰
	private String token;
	
	public JWTToken(String token) {
		this.token = token;
	}
	@Override
	public Object getPrincipal() {
		return token;
	}

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

}

4.token的生成和解析方式,請注意網上很多方式都是無效的,我花費一天時間試驗出來的下面方式有效。

JWTUtil.java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;

import java.util.Calendar;
import java.util.Date;


public class JWTUtil {
	public static final String TOKEN_HEADER = "Authorization";
	public static final String TOKEN_PREFIX = "Bearer ";
	private static final String SECRET = "jwtsecretdemo";

	public static final long EXPIRATION_REMEMBER = 24 * 60 * 60 * 7;


	public static boolean verify(String token, String yhbh, String yhmm) {
		Claims cl = getTokenBody(token);
		String tokenYhbh = cl.get("yhbh").toString();
		String tokenYhmm = cl.get("yhmm").toString();
		if(StringUtils.equals(yhbh,tokenYhbh) && StringUtils.equals(yhmm,tokenYhmm)){
			return true;
		}else{
			return false;
		}
	}

	/**
	 * 從token獲取使用者編號
	 * @param token
	 * @return
	 */
	public static String getYhbh(String token){
		try {
			return getTokenBody(token).get("yhbh").toString();
		}catch (Exception e){
			return null;
		}
	}

	/**
	 * 從token獲取使用者密碼
	 * @param token
	 * @return
	 */
	public static String getYhmm(String token){
		try {
			return getTokenBody(token).get("yhmm").toString();
		}catch (Exception e){
			return null;
		}
	}

	/**
	 * 取出所有自定義的key,value
	 * */
	private static Claims getTokenBody(String token){
		try {
			return Jwts.parser()
					.setSigningKey(SECRET)
					.parseClaimsJws(token)
					.getBody();
		}catch (Exception e){
			return null;
		}
	}

	public static String createToken(String yhbh,String yhmm){
		Date iatDate = new Date();
		// expire time
		Calendar nowTime = Calendar.getInstance();
		//有10天有效期
		nowTime.add(Calendar.DATE, 10);
		Date expiresDate = nowTime.getTime();
		Claims claims = Jwts.claims();
		claims.put("yhbh", yhbh);
		claims.put("yhmm", yhmm);
		String token = Jwts.builder().setClaims(claims).setExpiration(expiresDate)
				.signWith(SignatureAlgorithm.HS512, SECRET)
				.compact();
		return token;
	}

	public static void main(String[] args) {
		String yhbh = "10001";
		String yhmm = "111";
		String token = createToken(yhbh,yhmm);
		System.out.println("t="+token);
		String re = getYhbh(token);
		System.out.println(re);
	}
}

5.

MyRealm.java
import com.szht.cbhs.business.service.user.UserService;
import com.szht.cbhs.business.vo.user.UserInfo;
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.beans.factory.annotation.Autowired;


public class MyRealm extends AuthorizingRealm {

	@Autowired
	private UserService userService;
	
	@Override
	public boolean supports(AuthenticationToken token) {
		return token instanceof JWTToken;
	}

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		String yhbh = JWTUtil.getYhbh(principals.toString());
		UserInfo user = userService.selectUserByYhbh(yhbh);
		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
		simpleAuthorizationInfo.addRole(user.getJsbh());

		return simpleAuthorizationInfo;
	}

	/**
	 * 預設使用此方法進行使用者正確與否驗證,錯誤丟擲異常即可
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
		String token = (String) authenticationToken.getCredentials();
		// 解密獲得username,用於和資料庫進行對比
		String yhbh = JWTUtil.getYhbh(token);
		if (yhbh == null) {
			throw new AuthenticationException("token 無效!");
		}

		UserInfo user = userService.selectUserByYhbh(yhbh);
		if (user == null) {
			throw new AuthenticationException("使用者"+yhbh+"不存在") ;
		}
		
		if (!JWTUtil.verify(token, yhbh, user.getYhmm())) {
			throw new AuthenticationException("賬戶密碼錯誤!");
		}
		return new SimpleAuthenticationInfo(token, token, "my_realm");
	}

}

6.登入

UserController.java
import com.szht.cbhs.business.common.service.CommonDataCache;
import com.szht.cbhs.business.common.service.CommonService;
import com.szht.cbhs.business.service.user.UserService;
import com.szht.cbhs.business.vo.user.UserInfo;
import com.szht.cbhs.constant.CbhsSysConst;
import com.szht.cbhs.core.http.Result;
import com.szht.cbhs.core.msg.Message;
import com.szht.cbhs.jwt.JWTUtil;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@RestController
public class UserController {

    @Autowired
    private CommonService commonService;
    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public Result login(@RequestBody Map<String,String> jsonmap,
                        HttpServletResponse response) throws UnsupportedEncodingException {
        String yhbh = jsonmap.get("yhbh");
        String yhmm = jsonmap.get("yhmm");
        UserInfo user = userService.selectUserByYhbh(yhbh);
        if (user.getYhmm().equals(yhmm)) {
            String token = JWTUtil.createToken(yhbh,yhmm);
            response.setHeader("token", token);
            List<Message> msgs = new ArrayList<Message>();
            Message msg = new Message();
            msg.setMsg_info("登入成功");
            msg.setMsg_type(CbhsSysConst.INFO);
            msgs.add(msg);
            Result rs = new Result();
            rs.setSuccess(true);
            rs.setMessages(msgs);
            return rs;
        } else {
            throw new UnauthorizedException();
        }
    }

    @RequestMapping("/401")
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public Result unauthorized() {
        List<Message> msgs = new ArrayList<Message>();
        Message msg = new Message();
        msg.setMsg_code("401");
        msg.setMsg_info("未授權");
        msg.setMsg_type(CbhsSysConst.ERROR);
        msgs.add(msg);
        Result rs = new Result();
        rs.setSuccess(false);
        rs.setMessages(msgs);
        return rs;
    }
}

二、前端配置

Authorization裡放的是使用者密碼生成的token。

 let url = '/main/pzgl/pzfh/dfhpzjs';
      let data = { pzbh: this.search };
      let headers = { headers: { 'Authorization': 'eyJhbGciOiJIUzUxMiJ9.eyJ5aGJoIjoiMTAwMDEiLCJ5aG1tIjoiMTExIiwiZXhwIjoxNjAzMTY1MDE1fQ.GO4aGX_Ydc5EcImexrlDjbU7qH-XjeYmRmpEGa8z5BKktCaKf2_kWO5T0dIlbISdM_4IBs0s2DWJw1Bn8dlTcw' } };
      axios
        .post(url, data, headers)
        .then((response) => {
          this.dialogDesserts = JSON.parse(JSON.stringify(response.data.data));
          this.dialogDesserts.map((item, index) => {
            item.id = index;
          });
        })
        .catch((error) => {
          console.log(error);
        });

 

相關文章