- token类
- 自定义realm
- 自定义的jwtFilter用于访问拦截:
- shiroconfig
- controller
- vo对象
- 测试
jwt和shiro框架就不多介绍了,直接上实例代码吧。目前测试可正常登录、获得用户角色、访问接口时根据@requireRoles进行拦截。
token类import org.apache.shiro.authc.AuthenticationToken; //一般的登陆只需要校验账号和密码两个要素,默认的UsernamePasswordToken就能满足需求 //这个就类似UsernamePasswordToken //在JwtRealm中的授权部分,可以使用JwtUtil.decode(jwt).get("username")获取到username,使用username去数据库中查找到对应的权限,然后将权限赋值给这个用户就可以实现权限的认证了 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; } }自定义realm
@Slf4j public class JwtRealm extends AuthorizingRealm { @Override public boolean supports(AuthenticationToken token) { //这个token就是从过滤器中传入的jwtToken return token instanceof JWTToken; } @Autowired private IInowRoleUserService roleUserService; @Autowired private IInowRoleService roleService; @Autowired private IInowUserService userService; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { log.info("=========执行了授权==========================="); InowUser userName = (InowUser) principalCollection.getPrimaryPrincipal(); InowUser inowUser = userService.selectOneByName(userName.getUserName()); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); final InowRoleUser roleuser = roleUserService.getOne(new LambdaQueryWrapper自定义的jwtFilter用于访问拦截:().eq(InowRoleUser::getUserId, inowUser.getUserId())); List inowRoleList = new ArrayList<>(); inowRoleList.add(roleService.getOne(new LambdaQueryWrapper ().eq(InowRole::getId,roleuser.getRoleId())).getRoleName()); simpleAuthorizationInfo.addRoles(inowRoleList); //添加用户角色,用于@requireroles注解 return simpleAuthorizationInfo; } //认证 // 这里的 token是从 JWTFilter 的 executeLogin 方法传递过来的(请求头的token) // 只要调用了subject.login(token)方法,就会进入到realm的doGetAuthenticationInfo内。 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException { String token = (String)auth.getCredentials(); log.info("从auth获得的Token"+token); String userName = JWTUtil.getUsername(token); //用户不存在(这个在登录时不会进入,只有在token校验时才有可能进入) if(userName == null) { throw new UnknownAccountException(); } InowUser user = userService.selectOneByName(userName); if(Objects.isNull(user)) { throw new UnknownAccountException(); } String salt = "666666"; //自己随便写的一个加密盐 Md5Hash md5Hash = new Md5Hash(user.getUserId(), salt, 1024); if(!JWTUtil.verify(token,userName, md5Hash.toHex()))//学号当做用户密码 { throw new IncorrectCredentialsException(); } //toke过期 if(JWTUtil.isExpire(token)){ throw new ExpiredCredentialsException(); } return new SimpleAuthenticationInfo(user, token, getName()); } }
@Slf4j //@Component("jwtFilter") //去掉@Component注解,让filter不交给springbean管理,因为这会导致shiro内置的anon过滤器失效,具体原因还未找出 public class JwtFilter extends BasicHttpAuthenticationFilter { @Override protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { //在请求头中获取token HttpServletRequest httpServletRequest = (HttpServletRequest) request; String token = httpServletRequest.getHeader("Authorization"); //前端命名Authorization //token不存在 if(token == null || "".equals(token)){ Result result = new Result(); result.setCode(400); result.setMsg("token为空,无法访问"); out(response,result); return false; } //token存在,进行验证 JWTToken jwtToken = new JWTToken(token); try { SecurityUtils.getSubject().login(jwtToken); //通过subject,提交给myRealm进行登录验证 return true; } catch (ExpiredCredentialsException e){ Result result = new Result(); result.setCode(400); result.setMsg("登录已经过期"); out(response,result); e.printStackTrace(); return false; } catch (ShiroException e){ // 其他情况抛出的异常统一处理,由于先前是登录进去的了,所以都可以看成是token被伪造造成的 Result res = new Result(); res.setMsg("无效token"); out(response,res); e.printStackTrace(); return false; } } private void out(ServletResponse response, Result res) throws IOException { HttpServletResponse httpServletResponse = WebUtils.toHttp(response); ObjectMapper mapper = new ObjectMapper(); String jsonRes = mapper.writevalueAsString(res); httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setContentType("application/json; charset=utf-8"); httpServletResponse.getOutputStream().write(jsonRes.getBytes()); } @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { try { return executeLogin(request, response); //token验证 } catch (Exception e) { e.printStackTrace(); return false; } } // @Override // protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { // return super.onAccessDenied(request, response); // } // @Override // protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { // } }shiroconfig
@Configuration public class ShiroConfig { public logOutFilter mylogoutFilter() { logOutFilter MylogoutFilter = new logOutFilter(); MylogoutFilter.setLoginUrl("login");//设置退出后重定向的跳转地址 return MylogoutFilter; } @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(@Autowired @Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager, @Autowired ShiroFilterChainDefinition definition ){ JwtFilter jwtFilter = new JwtFilter(); ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(defaultWebSecurityManager);//设置安全管理器 //可以添加shiro的内置过滤器 //anon:无需认证就可以访问 authc:必须认证了才能用 oerms:拥有对某个资源的权限才能访问 bean.setLoginUrl("/login");//设置登录路径 //bean.setUnauthorizedUrl("/unlogin"); MapcontrollerfilterMap = new HashMap<>(); // 注销成功,则跳转到指定页面 filterMap.put("logout", mylogoutFilter()); filterMap.put("anon", new AnonymousFilter()); filterMap.put("jwt",jwtFilter); bean.setFilters(filterMap); bean.setFilterChainDefinitionMap(definition.getFilterChainMap()); return bean; } //@Bean public JwtFilter jwtFilter(){ return new JwtFilter(); } @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition(){ DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition(); Map map = new linkedHashMap<>(); map.put("/login","anon"); map.put("/swagger-ui.html","anon"); map.put("/doc.html","anon"); map.put(" @Bean public JwtRealm myUserRealm(){ JwtRealm userRealm = new JwtRealm(); return userRealm; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Autowired DefaultWebSecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean @DependsOn("lifecycleBeanPostProcessor") //DefaultAdvisorAutoProxyCreator: //BeanPostProcessor实现,它根据当前BeanFactory中的所有候选Advisor创建 AOP 代理。 public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); // 强制指定注解的底层实现使用 cglib 方案,防止重复代理和可能引起代理出错的问题 defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } @Bean public SubjectFactory subjectFactory() { return new JwtDefaultSubjectFactory(); } @Bean("securityManager") public DefaultWebSecurityManager securityManager(@Autowired JwtRealm myRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myRealm); // 关闭 ShiroDAO 功能 DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); DefaultSessionStorageevaluator defaultSessionStorageevaluator = new DefaultSessionStorageevaluator(); // 不需要将 Shiro Session 中的东西存到任何地方(包括 Http Session 中) defaultSessionStorageevaluator.setSessionStorageEnabled(false); subjectDAO.setSessionStorageevaluator(defaultSessionStorageevaluator); securityManager.setSubjectDAO(subjectDAO); //禁止Subject的getSession方法 securityManager.setSubjectFactory(subjectFactory()); return securityManager; } }
@ApiOperation("登录接口") @GetMapping public Result userLogin(@ApiParam(name="userId",value="用户学号",required=true) @RequestParam(value = "userId",required = true)String userId, @ApiParam(name="userName",value="用户姓名",required=true) @RequestParam(value = "userName",required = true)String userName) { // 1、获取Subject实例对象 //根据用户名获取正确用户信息 InowUser user = userService.selectOneByName(userName); if(user == null) { return Result.fail("无效用户,请检查您的姓名"); } //盐 + 输入的密码(注意不是用户的正确密码) + 1024次散列,作为token生成的密钥 String salt = "666666"; Md5Hash md5Hash = new Md5Hash(userId, salt, 1024); //生成token字符串 String token = JWTUtil.getJwtToken(userName, md5Hash.toHex()); //toHex转换成16进制,32为字符 log.info("从controller生成的Token"+token); JWTToken jwtToken = new JWTToken(token); Subject currentUser = SecurityUtils.getSubject(); // 4、认证 try { currentUser.login(jwtToken);// 传到Realm类中的方法进行认证 进入 AuthenticationInfo方法 //session.setAttribute("username", userName); InowUser nowUser = (InowUser) currentUser.getPrincipal(); InowUserVo userVo = new InowUserVo(); BeanUtils.copyProperties(nowUser,userVo); userVo.setToken(token); return Result.success(userVo); } catch (AuthenticationException e) { String msg = null; if (StringUtils.isNotEmpty(e.getMessage())) { msg = e.getMessage(); } return Result.fail("用户名与学号不匹配!"); } // } }vo对象
在vo中放了token属性的成员属性
@Data @JsonInclude(JsonInclude.Include.NON_EMPTY) public class InowUserVo implements Serializable { private static final long serialVersionUID = 1L; @NotBlank(message = "id不能为空!") @ApiModelProperty(value="用户id,学号或教师号",name="id",example="2020214283") private String userId; private Integer id; @NotBlank(message = "姓名不能为空!") @ApiModelProperty(value="用户姓名",name="userName",example="张三") private String userName; @ApiModelProperty(value="用户学院",name="userAcademy",example="计算机与信息学院") private String userAcademy; @ApiModelProperty(value="微信id",name="wechatId",example="微信id") private Long wechatId; @ApiModelProperty(value="性别",name="userSex",example="1男,2女") private byte userSex; @ApiModelProperty(value="最后登陆时间",name="loginDate") private Date loginDate; @ApiModelProperty(value="用户角色",name="roleName") private String roleName; private String token; }测试
登录接口:
访问一个标有注解@RequireRoles(“admin”)的方法:
在不加token时返回结果:
加一个没有Admin角色的Token访问结果:
用有admin角色的用户token访问结果:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)