一、什么是shiro
Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 来的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单地解决项目问题就好了。
二、SpringBoot引入shiro的依赖
org.apache.shiro
shiro-spring
1.9.1
com.auth0
java-jwt
3.11.0
io.jsonwebtoken
jjwt
0.9.0
三、引入shiro的配置
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;import org.apache.shiro.mgt.DefaultSubjectDAO;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.mgt.SubjectFactory;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import javax.servlet.Filter;import java.util.HashMap;import java.util.LinkedHashMap;import java.util.Map;/** * 描述: * */@Configurationpublic class ShiroConfig { @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); Map filterMap = new HashMap<>(); //这个地方其实另外两个filter可以不设置,默认就是 //filterMap.put("anon", new AnonymousFilter()); filterMap.put("jwt", new JwtFilter()); //filterMap.put("logout", new LogoutFilter()); shiroFilterFactoryBean.setFilters(filterMap); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/login/loginPage"); shiroFilterFactoryBean.setUnauthorizedUrl("/login/loginPage"); Map filterChainDefinitionMap = new LinkedHashMap<>(); // filterChainDefinitionMap.put("/login/ssoLogin", "anon"); filterChainDefinitionMap.put("/login/loginPage", "anon"); filterChainDefinitionMap.put("/login/loginCheck","anon"); filterChainDefinitionMap.put("/login/quit","anon"); filterChainDefinitionMap.put("/test2","anon"); filterChainDefinitionMap.put("/test1","anon"); filterChainDefinitionMap.put("/static/**","anon"); filterChainDefinitionMap.put("/favicon.ico","anon"); filterChainDefinitionMap.put("/", "anon"); //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证 filterChainDefinitionMap.put("/**", "jwt"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager(); defaultSecurityManager.setRealm(customRealm()); // 关闭 ShiroDAO 功能 DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); // 不需要将 Shiro Session 中的东西存到任何地方(包括 Http Session 中) defaultSessionStorageEvaluator.setSessionStorageEnabled(false); subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); defaultSecurityManager.setSubjectDAO(subjectDAO); //禁止Subject的getSession方法 defaultSecurityManager.setSubjectFactory(subjectFactory()); return defaultSecurityManager; } @Bean public CustomRealm customRealm() { return new CustomRealm(); } @Bean public SubjectFactory subjectFactory() { return new JwtDefaultSubjectFactory(); }}
四、配置JwtFilter
import lombok.extern.slf4j.Slf4j;import org.apache.shiro.web.filter.AccessControlFilter;import org.springframework.util.StringUtils;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/* * 自定义一个Filter,用来拦截所有的请求判断是否携带Token * isAccessAllowed()判断是否携带了有效的JwtToken * onAccessDenied()是没有携带JwtToken的时候进行账号密码登录,登录成功允许访问,登录失败拒绝访问 * */@Slf4jpublic class JwtFilter extends AccessControlFilter { /* * 1. 返回true,shiro就直接允许访问url * 2. 返回false,shiro才会根据onAccessDenied的方法的返回值决定是否允许访问url * */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return false; } /** * 返回结果为true表明登录通过 */ @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { //这个地方和前端约定,要求前端将jwtToken放在请求的Header部分 //所以以后发起请求的时候就需要在Header中放一个Authorization,值就是对应的Token HttpServletRequest request = (HttpServletRequest) servletRequest; String jwt = request.getHeader("Authorization"); if(StringUtils.isEmpty(jwt)){ jwt=request.getParameter("token"); } /* * 此处执行相关的业务逻辑,判断是否允许进行相应的登录 */ JwtToken jwtToken = new JwtToken(jwt); try { // 委托 realm 进行登录认证 //所以这个地方最终还是调用JwtRealm进行的认证 getSubject(servletRequest, servletResponse).login(jwtToken); //也就是subject.login(token) } catch (Exception e) { e.printStackTrace(); onLoginFail(servletResponse); //调用下面的方法向客户端返回错误信息 return false; } return true; } //登录失败时默认返回 401 状态码 private void onLoginFail(ServletResponse response) throws IOException { HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); //httpResponse.getWriter().write("login error"); httpResponse.sendRedirect("/login/loginPage"); }}
五、shiro默认是创建会话的,关闭相应的会话
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
public class JwtDefaultSubjectFactory extends DefaultWebSubjectFactory {
@Override
public Subject createSubject(SubjectContext context) {
// 不创建
session context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}
六、创建属于自己的JwtToken
import org.apache.shiro.authc.AuthenticationToken;//这个就类似UsernamePasswordTokenpublic class JwtToken implements AuthenticationToken { private String jwt; public JwtToken(String jwt) { this.jwt = jwt; } @Override//类似是用户名 public Object getPrincipal() { return jwt; } @Override//类似密码 public Object getCredentials() { return jwt; } //返回的都是jwt}
七、身份验证和权限验证
import com.south.wires.entity.db2.WkcrUser;import com.south.wires.utils.other.JwtUtil;import com.south.wires.utils.other.RedisUtil;import com.south.wires.utils.other.SpringUtil;import lombok.extern.slf4j.Slf4j;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.*;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.util.StringUtils;import java.util.HashSet;import java.util.Set;@Slf4jpublic class CustomRealm extends AuthorizingRealm { /*** 判断token是否是我们的这个jwttoekn */ @Override public boolean supports(AuthenticationToken token) { return token instanceof JwtToken; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set stringSet = new HashSet<>(); stringSet.add("user:show"); stringSet.add("user:admin"); info.setStringPermissions(stringSet); return info; } /*** 获取即将需要认证的信息*/ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String jwt = (String) authenticationToken.getPrincipal(); if (jwt == null) { throw new NullPointerException("jwtToken 不允许为空"); } //判断 JwtUtil jwtUtil = new JwtUtil(); /**自行执行相应的判断逻辑*/ return new SimpleAuthenticationInfo(jwt,jwt,"JwtRealm"); }}
留言与评论(共有 0 条评论) “” |