SpringBoot如何集成Shiro呢?
下文笔者讲述SpringBoot中集成Shiro的方法及示例分享,如下所示
Shiro简介
Shiro是Apache下的一个开源项目 Shiro是一个功能强大的安全框架,对外提供认证、授权、加密和会话管理等功能 对于任何一个应用程序 Shiro都可以提供全面的安全管理服务 而且Shiro这个框架使用非常简单 ===================================================== 那么如何让这个优秀的框架集成到Shiro中呢? 下文笔者将一一道来具体的集成方法,如下所示
集成步骤: 1.导入Shiro相关依赖 2.ShiroConfig 3.新建一个Realm类 4.新建一个JWTFilter 5.JwtToken 6.JwtUtils 7.全局异常处理 8.测试Controller
shiro所需依赖
导入 shiro-redis的starter包 jwt工具包 hutool工具包
<!-- shiro --> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis-spring-boot-starter</artifactId> <version>3.3.1</version> </dependency> <!-- huitool工具类 --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.3</version> </dependency> <!-- jwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
ShiroConfig代码的编写
@Configuration public class ShiroConfig { @Autowired JwtFilter jwtFilter; @Bean public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); // inject redisSessionDAO sessionManager.setSessionDAO(redisSessionDAO); // other stuff... return sessionManager; } @Bean public DefaultWebSecurityManager securityManager(AccountRealm accountRealm, SessionManager sessionManager, RedisCacheManager redisCacheManager) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm); securityManager.setSessionManager(sessionManager); securityManager.setCacheManager(redisCacheManager); /* * 关闭shiro自带的session */ DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); defaultSessionStorageEvaluator.setSessionStorageEnabled(false); subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); securityManager.setSubjectDAO(subjectDAO); return securityManager; } @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition(){ DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); Map<String,String> filterMap = new LinkedHashMap<>(); //主要通过注解方式校验权限 filterMap.put("/**","jwt"); chainDefinition.addPathDefinitions(filterMap); return chainDefinition; } @Bean("shiroFilterFactoryBean") public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,ShiroFilterChainDefinition shiroFilterChainDefinition){ ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); Map<String, Filter> filters = new HashMap<>(); filters.put("jwt", jwtFilter); shiroFilter.setFilters(filters); Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap(); shiroFilter.setFilterChainDefinitionMap(filterMap); return shiroFilter; } }
RedisSessionDAO和RedisCacheManager: 其功能将shiro中权限数据和会话信息能保存到redis中,实现会话共享 重写SessionManager和DefaultWebSecurityManager 同时DefaultWebSecurityManager中关闭shiro自带的session方式 需设置为false 此时用户就不再能通过session方式登录shiro 将采用jwt凭证登录。 在ShiroFilterChainDefinition中 我们不再通过编码形式拦截Controller访问路径 而是所有的路由都需要经过JwtFilter这个过滤器 然后判断请求头中是否含有jwt的信息 有就登录,没有就跳过。跳过之后 有Controller中的shiro注解进行再次拦截 如@RequiresAuthentication,这样控制权限访问。
新建一个Realm类
新建realm类: 需继承Shiro包下AuthorizingRealm类 并重写doGetAuthorizationInfo和doGetAuthenticationInfo方法 doGetAuthorizationInfo获取身份信息: 在这个方法中 可从数据库获取该用户的权限和角色信息 当调用权限验证时,就会调用此方法 doGetAuthenticationInfo: 在这个方法中 进行身份验证 login时调用例:Realm的源码
@Component public class AccountRealm extends AuthorizingRealm { @Autowired JwtUtils jwtUtils; @Autowired CardService cardService; @Override public boolean supports(AuthenticationToken token) { //realm支持jwt的凭证校验 return token instanceof JwtToken; } /** *获取身份信息,我们可以在这个方法中,从数据库获取该用户的权限和角色信息 当调用权限验证时,就会调用此方法 * */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } /** * 在这个方法中,进行身份验证 login时调用 * */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { JwtToken jwtToken = (JwtToken) authenticationToken; //这里演示就随便写的接口 到时候这里写你们登录验证时的业务操作即可 String userId = jwtUtils.getClaimByToken((String) jwtToken.getPrincipal()).getSubject(); Card card = cardService.getById(Long.valueOf(userId)); if(card == null){ throw new UnknownAccountException("账户不存在"); } AccountProfile accountProfile = new AccountProfile(); BeanUtil.copyProperties(card,accountProfile); return new SimpleAuthenticationInfo(accountProfile,jwtToken.getCredentials(),getName()); } } doGetAuthenticationInfo登录认证方法简介: 通过jwt获取到用户信息 判断用户的状态 最后异常就抛出对应的异常信息 否者封装成SimpleAuthenticationInfo返回给shiro
AccountProfile定义
AccountProfile 根据自身业务填写参数 登录成功之后返回的一个用户信息的载体 说白了就是一个用户的实体类 @Data public class AccountProfile implements Serializable { private Long id; private Date addTime; private String phone; private String name; }
创建JWTFilter实体类
@Component public class JwtFilter extends AuthenticatingFilter { @Autowired JwtUtils jwtUtils; @Override protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { //将servletRequest强转为HttpServletRequest HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; String jwt = httpServletRequest.getHeader("Authorization"); if (jwt.isEmpty()){ return null; } return new JwtToken(jwt); } @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; String jwt = httpServletRequest.getHeader("Authorization"); //这里为空将不再进行shiro登录处理 直接交给注解拦截就行了 if (StringUtils.isEmpty(jwt)){ return true; }else { //校验jwt Claims claim = jwtUtils.getClaimByToken(jwt); if(claim == null || jwtUtils.isTokenExpired(claim.getExpiration())){ throw new ExpiredCredentialsException("token已经失效,请重新登录"); } //执行登录处理 return executeLogin(servletRequest,servletResponse); } } @Override protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { //将异常返回给前端 HttpServletResponse servletResponse = (HttpServletResponse) response; Throwable throwable = e.getCause() == null ? e : e.getCause(); Result result = new Result(400, throwable.getMessage(), null); String jsonStr = JSONUtil.toJsonStr(result); try { servletResponse.getWriter().print(jsonStr); } catch (IOException ex) { } return false; } }
JwtToken
创建一个jwtToken实现AuthenticationToken
public class JwtToken implements AuthenticationToken { private String token; public JwtToken(String token){ this.token = token; } @Override public Object getPrincipal() { return token; } @Override public Object getCredentials() { return token; } }
JwtUtils
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.Date; /** * jwt工具类 */ @Slf4j @Data @Component @ConfigurationProperties(prefix = "jwtutils.jwt") public class JwtUtils { private String secret; private long expire; private String header; /** * 生成jwt token */ public String generateToken(long userId) { Date nowDate = new Date(); //过期时间 Date expireDate = new Date(nowDate.getTime() + expire * 1000); return Jwts.builder() .setHeaderParam("typ", "JWT") .setSubject(userId+"") .setIssuedAt(nowDate) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public Claims getClaimByToken(String token) { try { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); }catch (Exception e){ log.debug("validate is token error ", e); return null; } } /** * token是否过期 * @return true:过期 */ public boolean isTokenExpired(Date expiration) { return expiration.before(new Date()); } }
全局异常类编写
@RestControllerAdvice public class GlobalExceptionHeadler { @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(value = RuntimeException.class) public Result headler(RuntimeException e){ return new Result(400,e.getMessage(),null); } @ResponseStatus(HttpStatus.UNAUTHORIZED) @ExceptionHandler(value = ShiroException.class) public Result headler(ShiroException e){ return new Result(401,e.getMessage(),null); } }
全局返回对象定义
@Data @AllArgsConstructor @NoArgsConstructor public class Result<T> { private Integer code; private String message; private T data; public Result(Integer code,String message){ this(code,message,null); } }
配置文件
shiro-redis: enabled: true redis-manager: host: 127.0.0.1:6379 jwtutils: jwt: #加密密钥 secret: f1231x545as21xc5a4sz2c1sa #token有效时间 单位秒 expire: 604800 header: Authorization
编写Controller测试
@RequiresAuthentication @GetMapping("/test") @ResponseBody public Result<list<UserList>> getUserList(){ }
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。