SpringBoot - Shiro 前后端分离配置
前言
因为业务场景变化,公司项目要转变为前后端分离项目,这里讲解如何配置 shiro 作为前后端分离项目使用
思路
-
传递 sessionId 的方式
以前的方式是 Shiro 从 cookie 拿到 sessionId 验证来维持会话。现在我们使用在 Ajax 的请求头中传递 sessionId,并且需要重写 Shiro 获取 sessionId 的方式。
-
登录成功失败后的路由控制
前后端分离的项目中,路由完全由前端控制,请求只负责传递数据和验证回话,所以我们只负责像前端传递 json 数据,告知认证成功或失败的结果就行。
解决方案
-
传递 sessionId
-
重写获取 sessionId 的方法
MySessionManager.java
public class MySessionManager extends DefaultWebSessionManager { private static final String TOKEN = "token"; private static final String REFERENCE_SESSION_ID_SOURCE = "Stateless requese"; public MySessionManager() { super(); } @Override protected Serializable getSessionId(ServletRequest request, ServletResponse response) { String id = WebUtils.toHttp(request).getHeader(TOKEN); // 如果请求头中有包含 sessionId 的 token if (!StringUtils.isEmpty(id)) { request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCE_SESSION_ID_SOURCE); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); return id; } else { // 如果没有就按照 Shiro 默认的方法去取 return super.getSessionId(request, response); } } }
-
将我们自定义的 sessionManager 配置到 SecurityManager 中
ShiroConfig.java
/** * 安全管理器 */ @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm()); securityManager.setSessionManager(sessionManager()); return securityManager; } /** * sessionDAO 的方式 */ @Bean public SessionDAO sessionDAO(){ return new MemorySessionDAO(); } /** * 自定义 session 管理器 */ @Bean public SessionManager sessionManager() { MySessionManager mySessionManager = new MySessionManager(); mySessionManager.setSessionDAO(sessionDAO()); return mySessionManager; }
-
-
认证成功或失败后的返回结果
-
自建一个没有登录的请求进行拦截,返回 json 信息,覆盖掉 shiro 原有跳转 login 页面的方式的类
public class AjaxPermissionsAuthorizationFilter extends FormAuthenticationFilter { /** * Processes requests where the subject was denied access as determined by the isAccessAllowed method. * 被登陆拒绝后的请求 * @param request * @param response * @return * @throws Exception */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { JSONObject jsonObject = new JSONObject(); jsonObject.put("code", 1); jsonObject.put("msg", "未登录"); PrintWriter out = null; try { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); out = response.getWriter(); out.print(jsonObject); } catch (Exception e) { } finally { if(null != out) { out.flush(); out.close(); } } return false; } @Bean public FilterRegistrationBean registration(AjaxPermissionsAuthorizationFilter filter) { FilterRegistrationBean registration = new FilterRegistrationBean(filter); registration.setEnabled(false); return registration; } }
当然你也可以使用定义一个未登录的请求去返回 json 信息
-
将 Shiro 默认认证失败跳转 login.jsp 的方式替换为上述 Filter,并且将 login 登录的请求配置为可以匿名访问
ShiroConfig.java
@Bean ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, Filter> filterMap = new LinkedHashMap<>(); // 将 Shiro 默认认证失败跳转 login.jsp 的方式替换 filterMap.put("authc", new AjaxPermissionsAuthorizationFilter()); shiroFilterFactoryBean.setFilters(filterMap); LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/logout", "logout"); // 将 login 登录的请求配置为可以匿名访问,如果不匿名总会被认为未登录拦截 filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/", "anon"); filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; }
-
-
其它部分代码
-
LoginController.java
@Log("登录") @PostMapping("/login") @ResponseBody R ajaxLogin(String mobile, String password) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(mobile, password); try { subject.login(token); R r = new R(); r.put("token", subject.getSession().getId()); return r; } catch (AuthenticationException e) { return R.error(e.toString()); } }
-
UserRealm.java
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String mobile = (String) token.getPrincipal(); String password = new String((char[]) token.getCredentials()); if (mobile.equals("15512345678") && password .equals("12345") ) { SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(mobile, password, getName()); return info; } else { throw new UnknownAccountException("账号密码不正确"); } } }
-
TestController.java 用来检验是否配置正确
@RequestMapping("/test") @Controller public class TestController { @GetMapping("/testToken") @ResponseBody R testToken() { return R.ok(); } }
-
测试
验证登录成功
验证已登录的请求
验证账户密码不正确
验证未登录的请求
本文由 Shuaiyin 创作,采用 知识共享署名4.0
国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Sep 18,2019