springboot+shiro

项目背景:该项目用来为app项目提供数据,在项目中只返回接口数据,不返回视图,并禁用了session,自定义了过滤器和token。

项目中使用的技术:springboot+maven+shiro。需要先了解一哈。

首先对shiro框架做个说明:

  Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。其中有3个核心组件,Subject, SecurityManager 和 Realms。

  Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。
  Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
  SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
  Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。

  Realm是整个框架中为数不多的必须由设计者自行实现的模块。

先新建一个springboot项目,创建后配置好maven,项目结构如下:

  

其中的service,mapper等是用来整合mysql,本次项目就简暂不使用。

项目pom.xml文件引用jar包配置如下

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- log4j -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.2</version>
		</dependency>
		<!-- shiro  -->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring-boot-starter</artifactId>
			<version>1.4.0</version>
		</dependency>
		<!-- google  -->
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>18.0</version>
		</dependency>
		<!-- mysql -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.47</version>
		</dependency>

	</dependencies>

  在shiro文件夹下新建JWTToken

/**
 * 自定义shiro的token
 */
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;
    }
}

新建MyShiroRealm,定义权限,在app上,就如首页是无需用户登录的,即首页是用户登录不登录都是可以访问的,但是用户的个人信息就需要登录才能访问。

/**
 * 自定义Realm <领域>
 */
public class MyShiroRealm extends AuthorizingRealm{

    /**
     * 使用自定义token
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 权限配置
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        System.out.println("判断用户是否有权限访问此连接");
        String url = request.getRequestURI();
        String method = request.getMethod();
        System.out.println(url+"------------"+method);
        //设计的验证权限的逻辑内容
        return authorizationInfo;
    }

    /**
     * 主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 获取用户的token
        String authorization = String.valueOf(token.getPrincipal());
        //这里验证token和simpleAuthenticationInfo的信息
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(authorization, authorization, getName());
     //暂定密码为111,应该根据信息查询用户并判断用户的身份
if (!authorization.equals("111")){ throw new AuthenticationException("密码错误!"); } return authenticationInfo; } }

新建StatelessDefaultSubjectFactory

public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {

    @Override
    public Subject createSubject(SubjectContext context) {
        // 不创建session
        context.setSessionCreationEnabled(false);
        return super.createSubject(context);
    }

}

新建JWTFilter,过滤器。

/**
 * 自定义jwt过滤器
 */
public class JWTFilter extends AccessControlFilter {

    private Logger LOGGER     = LoggerFactory.getLogger(this.getClass());

    //过滤器过滤用户是否有权限
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        LOGGER.info("当前用户访问url:" + httpRequest.getRequestURL().toString());
        //根据Header取出相关信息,之后放入Token
        String authorization = httpRequest.getHeader("authorization");
        try {
            if (StringUtils.isEmpty(authorization)) {
                throw new AuthenticationException("请求错误!");
            }
            // 获取无状态Token
            JWTToken jwtToken = new JWTToken(authorization);
            // 委托给Realm进行登录,会跳转doGetAuthenticationInfo方法进行判断用户
            getSubject(request, response).login(jwtToken);
            boolean isContinue = true;
       //这里对需要用户登录但试无需访问权限的公共url进行了判断
if (httpRequest.getRequestURI().indexOf("index") >= 0){ isContinue = false; } /************ 公用的配置跳过权限验证 end ****************/ if (isContinue) { // 通过isPermitted 才能调用doGetAuthorizationInfo方法获取权限信息 getSubject(request, response).isPermitted(httpRequest.getRequestURI()); } } catch (AuthenticationException e) { response401(httpRequest, response, e.getMessage()); return false; } return true; } //在访问被拒绝运行 @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { System.out.println("请求被拒绝"); return false; } /** * 将非法请求跳转到 /401 */ private void response401(ServletRequest req, ServletResponse resp, String message) { try { req.setAttribute("message", message); req.getRequestDispatcher("/401").forward(req, resp); } catch (IOException ex) { LOGGER.error(ex.getMessage()); } catch (ServletException e1) { LOGGER.error(e1.getMessage()); } return; } }

新建GlobalExceptionResolver

/**
 * 异常处理
 */
public class GlobalExceptionResolver implements HandlerExceptionResolver {

    private static Logger logger = LoggerFactory.getLogger(GlobalExceptionResolver.class);

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
                                         Exception ex) {
        ModelAndView mv;
        // 进行异常判断。如果捕获异常请求跳转。
        if (ex instanceof UnauthorizedException) {
            mv = new ModelAndView("/user/unauth");
            return mv;
        } else {
            mv = new ModelAndView();
            FastJsonJsonView view = new FastJsonJsonView();
            // ex.printStackTrace();
            logger.error("shiro错误!!", ex);
            Map<String, Object> map = new HashMap<>();
            String beanString = JSON.toJSONString("服务器异常");
            map = JSON.parseObject(beanString, Map.class);
            view.setAttributesMap(map);
            mv.setView(view);
            return mv;
        }

    }
}

新建ShiroConfig

@Configuration
public class ShiroConfig {

    private final static Logger LOGGER = LoggerFactory.getLogger(ShiroConfig.class);

    //将自己的验证方式加入容器
    @Bean(name = "customRealm")
    public MyShiroRealm customRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        return myShiroRealm;
    }

    //权限管理,配置主要是Realm的管理认证
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager defaultWebSecurityManager(MyShiroRealm customRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        StatelessDefaultSubjectFactory statelessDefaultSubjectFactory = new StatelessDefaultSubjectFactory();
        securityManager.setSubjectFactory(statelessDefaultSubjectFactory);

        /*
         * 关闭shiro自带的session,详情见文档
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
     //将自定义的realm让其管理使用
        securityManager.setRealm(customRealm);
        securityManager.setSessionManager(defaultSessionManager());
        return securityManager;
    }

    /**
     * 会话管理类 禁用session
     */
    @Bean
    public DefaultSessionManager defaultSessionManager() {
        LOGGER.info("=============ShiroConfig.getDefaultSessionManager()======");
        DefaultSessionManager manager = new DefaultSessionManager();
        manager.setSessionValidationSchedulerEnabled(false);
        return manager;
    }

    //Filter工厂,设置对应的过滤条件和跳转条件
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        LOGGER.info("===========init shiroFilterFactory============");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 添加自己的过滤器并且取名为jwt
        Map<String, Filter> filterMap = Maps.newHashMap();
        filterMap.put("jwtFilter", new JWTFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        Map<String, String> filterChainDefinitionMap = Maps.newLinkedHashMap();
        // 注意过滤器配置顺序 不能颠倒
        // 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl
        filterChainDefinitionMap.put("/401", "anon");

        // 配置不会被拦截的链接 顺序判断
//        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/logout", "anon");
        filterChainDefinitionMap.put("/login", "anon");
     //除放行的url外,其他的url都让JWTFilter进行拦截过滤 filterChainDefinitionMap.put(
"/**","jwtFilter"); // 配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据 // shiroFilterFactoryBean.setLoginUrl("/logout"); //错误页面,认证不通过跳转 shiroFilterFactoryBean.setUnauthorizedUrl("/401"); // 登录成功后要跳转的链接 // shiroFilterFactoryBean.setSuccessUrl("/index"); // 未授权界面 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } //加入注解的使用,不加入这个注解不生效 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * 注册全局异常处理 * * @return */ @Bean(name = "exceptionHandler") public HandlerExceptionResolver handlerExceptionResolver() { return new GlobalExceptionResolver(); } }

新建UserController

@RestController
@RequestMapping("/user")
public class UserController{

    @GetMapping("/insert")
    public String insert(@RequestParam(value = "username", required = false)String username,@RequestParam(value = "password", required = false)String password){
        System.out.println(username+":"+password);
        return "ok";
    }
}

启动项目,进行访问

header中需要传anthorization参数并赋值为111,我们在isAccessAllowed方法中获取header并在doGetAuthenticationInfo方法中进行了密码效验。

控制台输出

可以在shiroFilterFactoryBean方法中进行放行,不再拦截过滤。

再次访问,输出为

可以看到没有对当前url进行拦截了。

猜你喜欢

转载自www.cnblogs.com/yinduang/p/10101332.html