springboot,vue,shiro整合 關於登錄認證功能


首先是session問題

傳統session認證
http協議是一種無狀態協議,即瀏覽器發送請求到服務器,服務器是不知道這個請求是哪個用戶發來的。為了讓服務器知道請求是哪個用戶發來的,需要讓用戶提供用戶名和密碼來進行認證。當瀏覽器第一次訪問服務器(假設是登錄接口),服務器驗證用戶名和密碼之后,服務器會生成一個sessionid(只有第一次會生成,其它會使用同一個sessionid),並將該session和用戶信息關聯起來,然后將sessionid返回給瀏覽器,瀏覽器收到sessionid保存到Cookie中,當用戶第二次訪問服務器是就會攜帶Cookie值,服務器獲取到Cookie值,進而獲取到sessionid,根據sessionid獲取關聯的用戶信息。

由於前后的分離開發,傳統的session方式不能用來做登錄認證了,因為存在前端是部署在ngnix代理服務器中,springboot是部署tomcat服務器中,兩個項目存在跨域問題,所以session不能共享,每次獲取session地址不同,session對象就不同

解決思路
1、首次登錄時,后端服務器判斷用戶賬號密碼正確之后, 生成 sessionId,設置過期時間保存到redis服務器中並返回給前端

    @ApiOperation(value = "登入")
    @PostMapping("/login")
    public ResultVO loginPost(@RequestParam("userName")String userName, @RequestParam("userPass")String userPass){
        //根據前端傳遞過來的name和passowrd生成shrio的UsernamePasswordToken
        userPass = new Md5Hash(userPass,userName,3).toString();
        UsernamePasswordToken token = new UsernamePasswordToken(userName, userPass);
        //寫shiro的認證邏輯
        Subject subject = SecurityUtils.getSubject();

        //因為可能出現異常,所以直接try catch
        try {
            //調用login方法,傳入token
            subject.login(token);
            String sid = (String) subject.getSession().getId();
            //如果登錄沒有出現異常的話,就可以通過getPrincinpal()獲取登錄用戶
            User user= (User)subject.getPrincipal();
            //獲取sessionId
            String sessionId = (String) subject.getSession().getId();
            //成功返回id和對象
            LoginVo loginVo=new LoginVo();
            loginVo.setUser(user);
            loginVo.setSessionId(sessionId);
            AppUser appUser=new AppUser();
            BeanUtils.copyProperties(user,appUser);
            return ResultVOUtil.success(loginVo);
        } catch (AuthenticationException e) {
            return ResultVOUtil.error(400,e.getMessage());
        }
    }

2、前端拿到后端返回的 sessionId, 存儲在 localStroage/sessionStroage里

const user = {
    state: {
        id:window.sessionStorage.getItem('id'),
        userId:null,
        userName:null,
        userNickname:null,
        userIcon:null,
        userNumber:null,
        mail:null,
        roleId:null
    },
    mutations: {
        //將id保存到sessionStorage里,id表示登陸狀態
        SET_ID: (state, data) => {
            state.id = data
            window.sessionStorage.setItem('id', data)
        },
        //獲取用戶信息
        SET_USER: (state, data) => {
            // 把用戶信息存起來
            state.userId = data.userId
            state.userName = data.userName
            state.userNickname = data.userNickname
            state.userIcon = data.userIcon
            state.userNumber = data.userNumber
            state.mail = data.mail
            state.roleId = data.roleId
        },
        //登出
        LOGOUT: (state) => {
            // 登出的時候要清除token
            state.id = null
            state.userId = null
            state.userName = null
            state.userNickname = null
            state.userIcon = null
            state.userNumber = null
            window.sessionStorage.removeItem('id')
        }
    },
    actions: {

    }
};

export default user;
 getUser(name,pass){
                login(name,pass).then(res => {
                    if(res.code==0){
                        this.$message({ message: "登錄成功", type: 'success' })
                        this.$store.commit('SET_ID', res.data.sessionId)
                        this.$store.commit('SET_USER', res.data.user)
                        this.centerDialogVisible=false
                    }else{
                        this.$message({ message: res.msg, type: 'error' })
                    }
                })
            },

3、前端每次路由跳轉到需要認證的頁面, 判斷 localStroage/sessionStroage有無 sessionId,沒有則跳轉到登錄頁

我的是局部路由的,大家根據不同需求寫

 beforeRouteEnter:(to,from,next)=>{
           next(vm => {
               vm.init()
               if (vm.$store.state.user.id==null){
                   vm.$message.error('你還沒有登錄,請先登錄');
                   next('/home');
               }
           })

4、封裝攔截器,每次請求接口,在請求頭里攜帶 sessionId

import axios from 'axios'
import { Notification } from 'element-ui';

export function request(config) {
  const instance = axios.create({
    baseURL: '/api',
    tiemout: 5000
  })

  instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

  instance.interceptors.request.use(config => {
    const id =window.sessionStorage.getItem('id')
    //  const id  =this.$store.state.user.id
    id && (config.headers.common['Authorization']  = 'beared'+id)
    return config
  }, err => {
    console.log(err);
  })

  instance.interceptors.response.use(res => {
    if (res.data.code == 401) {
      Notification({
        title: '溫馨提示',
        message: '您還沒有登錄,請登錄后再進行相關操作',
        position: 'bottom-right'
      })
    }
    return res.data
  }, err => {
    console.log(err);
  })

  return instance(config)
}

5、后端統一攔截判斷 請求頭有無 sessionId ,沒有或者 sessionId過期,返回401

6、前端得到 401 狀態碼,重定向到登錄頁面

登錄認證總結
用到的技術有路由守衛,axiox封裝請求響應攔截器,安全框架shiro,vuex和持久化插件(VuexPersistence)
關鍵點是session會話
把傳統的session會話交給shiro管理,重寫session校驗機制,設置成根據請求頭(sessionId)獲取session信息

public class CustomSessionManager extends DefaultWebSessionManager {
    /**
     * 設置會話時間
     */
    public CustomSessionManager() {
        super();
        setGlobalSessionTimeout(DEFAULT_GLOBAL_SESSION_TIMEOUT * 48);
    }

    /**
     * 請求頭Authorization:sessionid
     * 指定sessionid的獲取方法
     */
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        //獲取請求頭信息
        String id= WebUtils.toHttp(request).getHeader("Authorization");
        if (StringUtils.isEmpty(id)) {
            //為空則創建新的sessionid
            return super.getSessionId(request,response);
        }else {
            id=id.replaceAll("beared","");
            //在哪里獲取
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
            //id是什么
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            //是否要驗證
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        }
    }

}

配置shiro配置redis存儲session,這樣的好處,根據sessionId把session保存在數據庫中,既使重啟服務器也不會丟失session

前端實現:安裝vuex持久化插件VuexPersistence,把用戶信息和sessionId保存到vuex里面

封裝攔截器,每次請求接口,在請求頭里攜帶 vuex取出的sessionId,后端根據路徑判斷是否有權限或是否認證

我的shiro配置

@Configuration
public class ShiroConfig {


	/**
	 * 創建ShiroFilterFactoryBean
	 */
	@Bean
	public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		
		//設置安全管理器
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		
		//添加Shiro內置過濾器
		/**
		 * Shiro內置過濾器,可以實現權限相關的攔截器
		 *    常用的過濾器:
		 *       anon: 無需認證(登錄)可以訪問
		 *       authc: 必須認證才可以訪問
		 *       user: 如果使用rememberMe的功能可以直接訪問
		 *       perms: 該資源必須得到資源權限才可以訪問
		 *       roles: 該資源必須得到角色權限才可以訪問
		 */
        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
        filters.put("roles",shiroRoleFilter());
        filters.put("authc",shiroLoginFilter());

        Map<String,String> filterMap = new LinkedHashMap<String,String>();

       filterMap.put("/byuser/**","authc");
//        filterMap.put("/byuser/home","anon");

		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        shiroFilterFactoryBean.setFilters(filters);

		return shiroFilterFactoryBean;
	}

    @Bean(name="shiroLoginFilter")
    public ShiroLoginFilter shiroLoginFilter() {
        return new ShiroLoginFilter();
    }
    @Bean(name="shiroRoleFilter")
    public ShiroRoleFilter shiroRoleFilter() {
        return new ShiroRoleFilter();
    }

    @Bean(name="corsBasicFilter")
    public CorsBasicFilter corsBasicFilter() {
        return new CorsBasicFilter();
    }

	/**
	 * 創建DefaultWebSecurityManager
	 */
	@Bean(name="securityManager")
	public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("loginRealm")LoginRealm userRealm){
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		//關聯realm
		securityManager.setRealm(userRealm);
        //將自定義的會話管理器注冊到安全管理器中
        securityManager.setSessionManager(sessionManager());
        //將自定義的redis緩存管理器注冊到安全管理器中
//        securityManager.setCacheManager(cacheManager());
		return securityManager;
	}
	
	/**
	 * 創建Realm
	 */
	@Bean(name="loginRealm")
	public LoginRealm getRealm(){
		return new LoginRealm();
	}



    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;

    /**
     * 1.redis的控制器,操作redis
     */
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setTimeout(7000);
        return redisManager;
    }

    /**
     * 2.sessionDao
     */
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO sessionDAO = new RedisSessionDAO();
        sessionDAO.setRedisManager(redisManager());
        return sessionDAO;
    }

    /**
     * 3.會話管理器
     */
    public DefaultWebSessionManager sessionManager() {
        //自定義子類
        CustomSessionManager sessionManager = new CustomSessionManager();
       // sessionManager.setSessionDAO(redisSessionDAO());
        //禁用所有的Cookie 這樣自己設置的session就不起作用了
//        sessionManager.setSessionIdCookieEnabled(false);
//        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }

    /**
     * 4.緩存管理器
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }




    //開啟對shior注解的支持
//    @Bean
//    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
//        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
//        advisor.setSecurityManager(securityManager);
//        return advisor;
//    }

//	@Bean
//	public ShiroDialect getShiroDialect(){
//		return new ShiroDialect();
//	}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM