Springboot + shiro +jwt 实现登录和权限控制

Springboot + shiro +jwt 项目

Maven依赖

Springboot采用的是2.2.0 下面是jwt 和shiro的依赖

				<dependency>
            <groupId>com.auth0groupId>
            <artifactId>java-jwtartifactId>
            <version>3.2.0version>
        dependency>
 				<dependency>
            <groupId>org.apache.shirogroupId>
            <artifactId>shiro-springartifactId>
            <version>1.4.0version>
        dependency>

原先的项目有Realm我还是集成了自己的Realm来做用户名密码的校验然后我又写了一个JWTRealm也就是双Realm来做

首先需要的就是写一个JWTUtil

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.io.UnsupportedEncodingException;
import java.util.Date;

/**
 * @Author: naine
 * @Description:
 * @Date: 10:46 上午 2020/9/8
 */
public class JWTUtil {
    // 过期时间 24 小时
    private static final long EXPIRE_TIME = 60 * 24 * 60 * 1000;
    // 密钥
    private static final String SECRET = "Naine";
    public static String createToken(String username) {
        try {
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            // 附带username信息
            return JWT.create()
                    .withClaim("username", username)
                    //到期时间
                    .withExpiresAt(date)
                    //创建一个新的JWT,并使用给定的算法进行标记
                    .sign(algorithm);
        } catch (Exception e) {
            return null;
        }
    }
    public static boolean verify(String token, String username) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            //在token中附带了username信息
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .build();
            //验证 token
            verifier.verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }
    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }
    public static String sign(String username, String secret) {
        try {
            Date date = new Date(System.currentTimeMillis()+EXPIRE_TIME);
            Algorithm algorithm = Algorithm.HMAC256(secret);
            // 附带username信息
            return JWT.create()
                    .withClaim("username", username)
                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (Exception e) {
            return null;
        }
    }
}

生成属于JWT的token放弃shiro自带的UsernamePasswordToken

public class JWTToken implements AuthenticationToken {
    private String token;
		
    public JWTToken(String token) {
        this.token = token;
    }
    //获取用户名
    @Override
    public Object getPrincipal() {
        return  JWTUtil.getUsername(token);
    }
  	//获取密码
    @Override
    public Object getCredentials() {
        return token;

    }
}

生成属于JWT的Realm

Shiro的底层通过JWTCredentialsMatcher方法来验证用户名密码对不对,但是现在我们用的是token这里得改成token是不是正确的所以改掉这个方法

public class JwtRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private PermissionService permissionService;
  
    public JwtRealm(){
      	// JWT方法才是真正验证token的地方
        this.setCredentialsMatcher(new JWTCredentialsMatcher());
    }
  	//判断AuthenticationToken 参数是不是 JWTToken类型如果是才进入
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken ;
    }
		//授权方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String username = JWTUtil.getUsername(principalCollection.toString());
        Role roles = roleService.selectRolebyusername(username);
        List<Permission> permissions = permissionService.selectPermissionbyusename(username);
        SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
        if (roles != null) {
            s.addRole(roles.getName());
        } else {
            return null;
        }

        Set<String> permissions1 = new HashSet<>();
        if (!permissions.isEmpty() && permissions.get(0) != null) {

            for (Permission permission : permissions) {
                permissions1.add(permission.getPerms().trim());
            }
            s.setStringPermissions(permissions1);
        } else {
            return null;
        }
        return s;
    }
		//登录方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
     		String token = (String) authenticationToken.getCredentials();
        return new SimpleAuthenticationInfo(token, token, this.getName());
    }
}

生成JWTCredentialsMatcher

在这个方法里true就是登陆成功 false就是登陆失败, 用JWTUtil里面来验证token是不是正确的,这里可能有和redis做比较的,但是思路都是差不多的就是比对你的token是不是正确的,如果正确就是登陆成功,错误就是登陆失败

@Slf4j
public class JWTCredentialsMatcher implements CredentialsMatcher {
    @Override
    public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
        String token = (String) authenticationToken.getCredentials();
        // 解密获得username,用于和数据库进行对比
        String username = JWTUtil.getUsername(token);
        try {
            JWTUtil.verify(token,username);
            return true;
        } catch (Exception e) {
            log.error("Token Error:{}", e.getMessage());
        }
        return false;
    }

添加自定义JWT过滤器

在这个过滤器中用来替代shiro的验证权限的自带的过滤器

@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        return false;
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("token");
        JWTToken jwtToken = new JWTToken(token);
        // 提交给realm进行登入,如果错误它会抛出异常并被捕获
        getSubject(request, response).login(jwtToken);
        // 如果没有抛出异常则代表登入成功,返回true
        return true;

    }
		//首先会进入这个方法
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (isLoginAttempt(request, response)) {
            //如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确
            try {
                executeLogin(request, response);
                return true;
            } catch (Exception e) {
                //token 错误
                    throw new Exception(300, "请登录");
            }
        }else {
            throw new MyException(300, "请登录");
        }
        //如果请求头不存在 token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true


    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        return super.onLoginFailure(token, e, request, response);
    }
		// 查看请求头是否带token
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("token");
        return token != null;
    }

}

关闭shiro自带sessionid不使用session来登录鉴权

public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
    @Override
    public Subject createSubject(SubjectContext context)  {
        context.setSessionCreationEnabled(false);
        return super.createSubject(context);
    }
}

配置shiro

将上一步的过滤器加入shiro过滤器,过滤器不需要交给Spring管理,不然过滤器就变成全部过滤器了。将authc 改为 jwt ,使用我们自己写的过滤器,RetryLimitHashedCredentialsMatcher() 这个是另一个验证账户名密码的Realm里面的和JWTRealm 无关 当时是因为要集成验证码登录的功能

@Configuration
public class ShiroConfigBean {
 		//配置过滤器
    @Bean
    public ShiroFilterFactoryBean shiroFilter(){
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
      	//添加过滤器
        Map<String,Filter> filterMap =shiroFilterFactoryBean.getFilters();
        filterMap.put("jwt", new JwtFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        
        shiroFilterFactoryBean.setSecurityManager(securityManager(new
                                                                  RetryLimitHashedCredentialsMatcher()));
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/jwt/login","anon");
        filterChainDefinitionMap.put("/**", "jwt");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
  	//配置 securityManager
    @Bean
    public DefaultWebSecurityManager securityManager(@Qualifier("RetryLimitHashedCredentialsMatcher") RetryLimitHashedCredentialsMatcher matcher){
     DefaultWebSecurityManager defaultWebSecurityManager=new DefaultWebSecurityManager();
      	//关闭shiro的seesionid
        DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
        DefaultWebSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
        sessionStorageEvaluator.setSessionStorageEnabled(false);
        defaultSubjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator);
        defaultWebSecurityManager.setSubjectFactory(new StatelessDefaultSubjectFactory());
      	//开始shiro验证策略当前策略一个Realm 通过代表全部通过
        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
        authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
        defaultWebSecurityManager.setAuthenticator(authenticator);
      //配置多个Realm
   defaultWebSecurityManager.setRealms(Arrays.asList(jwtShiroRealm(),myshiroRealm(matcher)));
        return defaultWebSecurityManager;
    }
  //JwtRealm
  @Bean(name = "jwtRealm")
    public JwtRealm jwtShiroRealm() {
        JwtRealm myShiroRealm = new JwtRealm();
        return myShiroRealm;
    }
  //开启shiro注解
  @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

登录接口

这里也可以写自己的用户名密码的比对总之就是验证成功创建token

@PostMapping("/login")
    public Map<String, Object> login(User user, HttpSession httpSession, String vcode, boolean remember, ServletRequest request, HttpServletResponse response, HttpServletRequest hrequest, String cid) {
        Subject subject = SecurityUtils.getSubject();
        Map<String, Object> objectMap = new HashMap<>();
        EasyTypeToken token = new EasyTypeToken(user.getUsername(), user.getPassword(), remember);
        try {
            subject.login(token);
            objectMap.put("code", "0");
            objectMap.put("msg", "登录成功");
            objectMap.put("token", JWTUtil.createToken(user.getUsername()));
            log.info(user.getUsername() + "密码登录成功");
        } catch (IncorrectCredentialsException e) {
            objectMap.put("code", "1");
            objectMap.put("msg", "用户名密码错误");
            log.info(user.getUsername() + "登录密码错误");
        } catch (LockedAccountException e) {
            objectMap.put("code", "2");
            objectMap.put("msg", "登录失败,该用户已被冻结");
            log.info(user.getUsername() + "登录失败,该用户已被冻结");
        } catch (UnknownAccountException e) {
            objectMap.put("code", "2");
            objectMap.put("msg", "请注册");
            log.info(user.getUsername() + "没有该用户");
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
            throw new MyException(LoginEnum.UNKNOWN_ERROR.getCode(), LoginEnum.UNKNOWN_ERROR.getMsg());
        }
        return objectMap;
    }

登录成功截图

Springboot + shiro +jwt 实现登录和权限控制_第1张图片

权限接口

image-20200916162449779

Springboot + shiro +jwt 实现登录和权限控制_第2张图片

Springboot + shiro +jwt 实现登录和权限控制_第3张图片

你可能感兴趣的:(Springboot,jwt,shiro,spring,boot)